From 79e5dc64c67f2519d4a06d0485f57c83e5f212dd Mon Sep 17 00:00:00 2001 From: Ivan FB Date: Fri, 12 Jun 2026 17:30:08 +0200 Subject: [PATCH] fix(pool): deinit context resources in full teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ffi/ffi_context_pool.nim | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ffi/ffi_context_pool.nim b/ffi/ffi_context_pool.nim index 179fb8f..e39f077 100644 --- a/ffi/ffi_context_pool.nim +++ b/ffi/ffi_context_pool.nim @@ -50,11 +50,19 @@ proc destroyFFIContext*[T]( ## for process/pool shutdown — normal destruction uses releaseFFIContext. ctx.stopAndJoinThreads().isOkOr: return err("destroyFFIContext(pool): " & $error) + # Close the ThreadSignalPtr fds and deinit the lock + event registry/queue + # BEFORE the slot is marked for rebuild. createFFIContext's rebuild path reruns + # initContextResources (initLock / initEventRegistry / initEventQueue + fresh + # signals); skipping deinit here would re-init still-live locks (UB) and orphan + # the old fds — the very leak this teardown exists to prevent. + let deinitRes = ctx.deinitContextResources() for i in 0 ..< MaxFFIContexts: if pool.contexts[i].addr == ctx: pool.initialized[i].store(false) break ctx.release() + deinitRes.isOkOr: + return err("destroyFFIContext(pool): " & $error) return ok() proc isValidCtx*[T](pool: var FFIContextPool[T], ctx: pointer): bool =