mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-23 01:39:26 +00:00
fix(context): pin myLib as a GC root under refc
myLib lives in non-GC `createShared` memory, so under --mm:refc a GC-managed lib object stored there is invisible to the cycle collector and gets reclaimed mid-operation under sustained request load — a use-after-free that crashes deep in the lib (e.g. a held chronos AsyncLock). Take a GC_ref once a handler installs myLib (tracked by FFIContext.myLibRefd) and GC_unref in freeLib so a later recycle/create can re-pin. Guarded to refc + ref types; orc tracks it precisely. Also wrap freeLib's `=destroy` in try/except: it is conservatively typed as raising, and recycleContext (its async caller) is `raises: []`, so the library would not compile under orc/arc without this. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
85931bad92
commit
342fefa623
@ -38,6 +38,11 @@ type CtxLifecycle {.pure.} = enum
|
||||
type FFIContext*[T] = object
|
||||
myLib*: ptr T
|
||||
# main library object (e.g., Waku, LibP2P, SDS, the one to be exposed as a library)
|
||||
myLibRefd: bool
|
||||
# refc only: whether we hold a GC_ref on myLib[]. myLib lives in non-GC
|
||||
# shared memory, so a GC-managed lib object stored there is invisible to
|
||||
# refc's cycle collector and gets reclaimed mid-use under sustained load.
|
||||
# Pinned in processRequest, released in freeLib. Unused under orc/arc.
|
||||
ffiThread: Thread[(ptr FFIContext[T])]
|
||||
# represents the main FFI thread in charge of attending API consumer actions
|
||||
watchdogThread: Thread[(ptr FFIContext[T])]
|
||||
@ -243,6 +248,15 @@ proc processRequest[T](
|
||||
"Async error in processRequest for " & reqId & ": " & exc.msg
|
||||
)
|
||||
|
||||
# Pin the lib object as a GC root once a handler has installed it. Under refc
|
||||
# myLib lives in non-GC shared memory, invisible to the cycle collector, so it
|
||||
# gets reclaimed mid-operation under sustained load. orc tracks it precisely.
|
||||
when defined(gcRefc):
|
||||
when T is ref:
|
||||
if not ctx.myLibRefd and not ctx.myLib.isNil():
|
||||
GC_ref(ctx.myLib[])
|
||||
ctx.myLibRefd = true
|
||||
|
||||
## handleRes may raise (OOM, GC setup) even though it is rare.
|
||||
try:
|
||||
handleRes(res, request)
|
||||
@ -254,10 +268,16 @@ proc freeLib[T](ctx: ptr FFIContext[T]) {.gcsafe.} =
|
||||
return
|
||||
|
||||
when not defined(gcRefc):
|
||||
{.cast(gcsafe).}:
|
||||
`=destroy`(ctx.myLib[])
|
||||
try:
|
||||
{.cast(gcsafe).}:
|
||||
`=destroy`(ctx.myLib[])
|
||||
except Exception:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
when T is ref:
|
||||
if ctx.myLibRefd:
|
||||
GC_unref(ctx.myLib[])
|
||||
ctx.myLibRefd = false
|
||||
freeShared(ctx.myLib)
|
||||
ctx.myLib = nil
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user