destroyFFIContext stopped/joined the worker threads and marked the slot for
rebuild, but no longer deinited the context — so the six ThreadSignalPtr fds
were orphaned every full teardown (the exact leak this path exists to prevent),
and the still-initialised Lock + event registry/queue locks were left live.
createFFIContext's rebuild path (initialized == false) reruns
initContextResources, which calls initLock / initEventRegistry / initEventQueue
and installs fresh signals over the stale handles — re-initialising a live lock
is UB. Restore the deinitContextResources() call (as the pre-recycle code did)
before marking the slot uninitialised so the rebuild starts from clean state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Call initializeLibrary() (setupForeignThreadGc) in the `.ffi.` request
wrapper and in add/remove_event_listener so a foreign (Go) caller thread
has an initialised Nim heap before any allocation ($reqTypeName /
$eventName / registry ops). Without it such a thread segfaults in the
allocator under GC pressure — the production unwrap SIGSEGV.
- recycleContext resets the event registry/queue + stuck flag on park so a
reused pool slot starts clean.
- ffiDtor doc/cleanup for the async recycle ABI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>