The branch carried a dated `[0.2.0] - 2026-06-04` release section and
`version = "0.2.0"` while the active release line is still 0.1.x, which
misrepresents unshipped work as a released version. Treat 0.1.4 as the latest
release and move everything above it back under `[Unreleased]`, merging the
0.2.0 bullets into the existing Added/Changed/Fixed groups. Set the package
version to 0.1.4 and realign the example `requires` (>= 0.2.0 -> >= 0.1.4) so
they stay satisfiable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
The recycle/async-destroy work changed the Nim `ffiDtor` export from
`int destroy(ctx)` to `int destroy(ctx, callback, userData)`, but the C++
and Rust generators still emitted the 1-arg signature. Foreign callers
therefore passed only `ctx`; inside Nim, `callback`/`userData` held
uninitialised register garbage. `requestRecycle` stored the garbage
callback and the recycle handler later invoked it — a jump through a wild
pointer that segfaulted in every C++ E2E / ASan / TSan job (the crash
surfaced at teardown, after each test's assertions had already passed).
Generate the 3-arg ABI and have the destructor/Drop block on the recycle
callback via the existing sync-call helper, so the pool slot is fully
drained and parked before the handle goes away — otherwise rapid
create/destroy churn (StressShortLivedPerThreadContext, ThreadedHammer)
could outrun the recycle and exhaust the pool.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The noSignalHandler compile-time guard rejects any build of the ffi
modules that doesn't set -d:noSignalHandler (embedded) or
-d:ffiAllowSignalHandler (standalone). test.yml compiled the unit tests
with a raw `nim c` missing the flag, so every per-test job failed at the
guard. The nimble test tasks already pass it; mirror that here.
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>
A nim-ffi library is loaded into a foreign host (Go/Rust/...) that must
own OS signal handling; if the Nim runtime installs its own handlers it
clobbers the host's (e.g. Go's SIGSEGV -> sigpanic recovery, stack
growth, goroutine preemption), turning recoverable faults into hard
process crashes. The flag can only be set on the consumer's final build
command, so a dependency cannot inject it -- but it CAN refuse to
compile without it. Add a compile-time guard so any consumer that omits
-d:noSignalHandler fails the build with an actionable message instead of
crashing at runtime (the cause of a real status-go regression).
Standalone Nim binaries (nim-ffi's own tests) build with
-d:ffiAllowSignalHandler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CBOR is the headline 0.2.0 feature, not 0.1.4: at v0.1.4 serial.nim was
still JSON/string-based, so the prior CBOR attribution was wrong. Also
complete the 0.2.0 scope (events, registry, codegen) ahead of tagging.
Date 0.1.4 by its last functional change (#14, 2026-05-13) rather than
the later changelog/version-bump commits, so the version reflects when
its code actually settled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* rust examples: sync main.rs + tokio main.rs demoing the listener API
Adds two bundled examples to the generated Rust crate:
- examples/main.rs: sync flow using std::sync::mpsc to bridge a typed
on_echo_fired listener into main + a wildcard add_event_listener
that uses decode_event_payload::<EchoEvent>(envelope) for the
matching event id.
- examples/tokio_main.rs: same shape via #[tokio::main] +
tokio::sync::mpsc.
Bumps generateCargoToml to ship `[dev-dependencies]` with tokio's
`rt-multi-thread` + `macros` features so the bundled examples can use
#[tokio::main] without polluting the library's runtime profile.
Run with `cargo run --example main` (set DYLD_LIBRARY_PATH=<repo> on
macOS until build.rs emits an rpath).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* simplify examples
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* protect against mem leak in case of failures sending requests to ffi thread
* better cleanup if failures in createFFIContext
* avoid dangling cstring in handleRes under ARC/ORC
* better resource cleanup in destroyFFIContext
* invoke onNotResponding if failure in destroyFFIContext
* correct seq copy in alloc
* make sure the lock is init before cleanUpResources
* better possible exception handling in processReq
* guard allocSharedSeq if given seq is empty
* enhance error handling in ffi_context
* add new tests and some corrections