mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-08-06 23:13:17 +00:00
91 lines
3.3 KiB
Nim
91 lines
3.3 KiB
Nim
import std/[atomics, sysatomics]
|
|
import results
|
|
import ./ffi_context
|
|
|
|
const MaxFFIContexts* = 32
|
|
|
|
type
|
|
StaticCtxState = enum
|
|
## Lifecycle of the pool's `{.ffiStatic.}` context; see `staticFFIContext`.
|
|
StaticCtxNone
|
|
StaticCtxCreating
|
|
StaticCtxReady
|
|
|
|
FFIContextPool*[T] = object
|
|
## Fixed pool. Bounds ThreadSignalPtr fds at MaxFFIContexts * 2.
|
|
slots: array[MaxFFIContexts, FFIContext[T]]
|
|
inUse: array[MaxFFIContexts, Atomic[bool]]
|
|
staticCtx: Atomic[pointer]
|
|
staticState: Atomic[StaticCtxState]
|
|
|
|
proc acquireSlot[T](pool: var FFIContextPool[T]): Result[ptr FFIContext[T], string] =
|
|
for i in 0 ..< MaxFFIContexts:
|
|
var expected = false
|
|
if pool.inUse[i].compareExchange(expected, true):
|
|
return ok(pool.slots[i].addr)
|
|
err("FFI context pool exhausted (max " & $MaxFFIContexts & " contexts)")
|
|
|
|
proc releaseSlot[T](pool: var FFIContextPool[T], ctx: ptr FFIContext[T]) =
|
|
for i in 0 ..< MaxFFIContexts:
|
|
if pool.slots[i].addr == ctx:
|
|
pool.inUse[i].store(false)
|
|
return
|
|
|
|
proc createFFIContext*[T](
|
|
pool: var FFIContextPool[T]
|
|
): Result[ptr FFIContext[T], string] =
|
|
let ctx = pool.acquireSlot().valueOr:
|
|
return err("createFFIContext: acquireSlot failed: " & $error)
|
|
initContextResources(ctx).isOkOr:
|
|
pool.releaseSlot(ctx)
|
|
return err("createFFIContext: initContextResources failed: " & $error)
|
|
ok(ctx)
|
|
|
|
proc destroyFFIContext*[T](
|
|
pool: var FFIContextPool[T], ctx: ptr FFIContext[T]
|
|
): Result[void, string] =
|
|
## On thread-exit timeout the slot is leaked; closing live-thread resources is unsafe.
|
|
ctx.stopAndJoinThreads().isOkOr:
|
|
return err("destroyFFIContext(pool): " & $error)
|
|
# Required: next acquisition would otherwise re-init a live lock (UB).
|
|
let deinitRes = ctx.deinitContextResources()
|
|
pool.releaseSlot(ctx)
|
|
deinitRes.isOkOr:
|
|
return err("destroyFFIContext(pool): " & $error)
|
|
ok()
|
|
|
|
proc staticFFIContext*[T](
|
|
pool: var FFIContextPool[T]
|
|
): Result[ptr FFIContext[T], string] =
|
|
## The pool's `{.ffiStatic.}` context: a static proc has no ctx of its own, but
|
|
## its handler still needs an FFI thread. Created on first use and never
|
|
## destroyed, so it holds a slot for good and `pool` must outlive its threads —
|
|
## only ever call this on the global `declareLibrary` emits. `myLib` stays the
|
|
## zero value; a static handler must not touch it. A failed create resets to
|
|
## `StaticCtxNone` so the spinning losers retry instead of hanging.
|
|
while true:
|
|
case pool.staticState.load()
|
|
of StaticCtxReady:
|
|
return ok(cast[ptr FFIContext[T]](pool.staticCtx.load()))
|
|
of StaticCtxCreating:
|
|
cpuRelax()
|
|
of StaticCtxNone:
|
|
var expected = StaticCtxNone
|
|
if not pool.staticState.compareExchange(expected, StaticCtxCreating):
|
|
continue
|
|
let ctx = pool.createFFIContext().valueOr:
|
|
pool.staticState.store(StaticCtxNone)
|
|
return err("staticFFIContext: " & error)
|
|
pool.staticCtx.store(cast[pointer](ctx))
|
|
pool.staticState.store(StaticCtxReady)
|
|
return ok(ctx)
|
|
|
|
proc isValidCtx*[T](pool: var FFIContextPool[T], ctx: pointer): bool =
|
|
## Rejects nil / dangling pointers at the API boundary.
|
|
if ctx.isNil():
|
|
return false
|
|
for i in 0 ..< MaxFFIContexts:
|
|
if cast[pointer](pool.slots[i].addr) == ctx:
|
|
return pool.inUse[i].load()
|
|
false
|