nim-ffi/ffi/codegen/templates/cpp/context_rule_of_5.hpp.tpl
Ivan FB 6bc626946e
fix(codegen): emit 3-arg async destroy ABI in C++/Rust bindings
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>
2026-06-12 16:12:33 +02:00

28 lines
1.3 KiB
Smarty

// Special-member policy: this class owns a {{LIB}} context, which in
// turn owns the library's worker thread(s) and internal state. Moving
// such an object out from under a caller silently tears that state
// down and is easy to misuse (e.g. storing in a container that
// relocates its elements). It also has no clean analogue in the other
// binding languages we generate. So copies and moves are both
// deleted; ownership is transferred via {{CTX}}::create returning a
// std::unique_ptr<{{CTX}}>. The destructor still releases the
// context.
~{{CTX}}() {
if (ptr_) {
// `{{LIB}}_destroy` is non-blocking at the C ABI: it parks the
// context for reuse and reports the outcome via the callback. Block
// here until that callback fires so the pool slot is fully drained
// and parked before this object goes away otherwise a rapid
// create/destroy churn could outrun the recycle and exhaust the pool.
(void)ffi_call_([this](FFICallback cb, void* ud) {
return {{LIB}}_destroy(ptr_, cb, ud);
}, timeout_);
ptr_ = nullptr;
}
}
{{CTX}}(const {{CTX}}&) = delete;
{{CTX}}& operator=(const {{CTX}}&) = delete;
{{CTX}}({{CTX}}&&) = delete;
{{CTX}}& operator=({{CTX}}&&) = delete;