2026-05-16 01:08:42 +02:00
|
|
|
// ============================================================
|
|
|
|
|
// Synchronous call helper
|
|
|
|
|
// ============================================================
|
2026-05-26 09:18:12 -03:00
|
|
|
// Guarded so two nim-ffi headers can share a translation unit.
|
|
|
|
|
#ifndef NIM_FFI_SYNC_CALL_HELPER_HPP_INCLUDED
|
|
|
|
|
#define NIM_FFI_SYNC_CALL_HELPER_HPP_INCLUDED
|
2026-05-16 01:08:42 +02:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
struct FFICallState_ {
|
|
|
|
|
std::mutex mtx;
|
|
|
|
|
std::condition_variable cv;
|
|
|
|
|
bool done{false};
|
|
|
|
|
bool ok{false};
|
|
|
|
|
std::vector<std::uint8_t> bytes;
|
|
|
|
|
std::string err;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
inline void ffi_cb_(int ret, const char* msg, size_t len, void* ud) {
|
feat(ffi): RET_STALE_WARN progress callback replacing handler timeout
nim-ffi never times a handler out: a hard-cancel mid-call into the
underlying library (Waku/libp2p) can leave it half-applied. Instead, while
a request is still in flight its result callback receives a non-terminal
RET_STALE_WARN every StaleWarnInterval (5s default, Android's ANR interval;
-d:ffiStaleWarnIntervalMs to override), carrying the elapsed milliseconds as
a decimal string. The request always ends with exactly one terminal
RET_OK/RET_ERR — the dev decides what to do with a slow one, nim-ffi does
not decide for them.
This replaces the never-released per-proc {.ffi: "timeout = <ms>".} override
and the defaultRequestTimeout context field (issue #93), whose hard abort
unblocked the caller early. The stale-warn interval is an internal context
seam (staleWarnInterval), not a per-proc pragma.
Codegen: emit RET_STALE_WARN in the C/C++ headers, and guard every generated
trampoline (C, C++, Rust, and the Nim abi=c reply trampolines) to early-return
on the non-terminal code so the repeated callback can't double-free the
one-shot reply box. Typed wrappers ignore the progress signal for now; it is
delivered at the raw result-callback boundary.
Closes #126.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:49:33 +02:00
|
|
|
// NIMFFI_RET_STALE_WARN (3) is a non-terminal progress ping: the request is
|
|
|
|
|
// still running. This blocking wrapper only reports the final result, so
|
|
|
|
|
// ignore it WITHOUT touching `ud` — a terminal callback still owns the
|
|
|
|
|
// shared handle and will free it.
|
|
|
|
|
if (ret == 3) return;
|
|
|
|
|
|
2026-05-16 01:08:42 +02:00
|
|
|
// ffi_call_ heap-allocated a shared_ptr and passed its address as ud;
|
|
|
|
|
// take ownership here so it's freed on every exit path.
|
|
|
|
|
std::unique_ptr<std::shared_ptr<FFICallState_>> handle(
|
|
|
|
|
static_cast<std::shared_ptr<FFICallState_>*>(ud));
|
|
|
|
|
FFICallState_& s = **handle;
|
|
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(s.mtx);
|
|
|
|
|
s.ok = (ret == 0);
|
|
|
|
|
if (msg && len > 0) {
|
|
|
|
|
const auto* p = reinterpret_cast<const std::uint8_t*>(msg);
|
|
|
|
|
if (s.ok) s.bytes.assign(p, p + len);
|
|
|
|
|
else s.err.assign(msg, len);
|
|
|
|
|
}
|
|
|
|
|
s.done = true;
|
|
|
|
|
s.cv.notify_one();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 12:35:49 -03:00
|
|
|
inline Result<std::vector<std::uint8_t>> ffi_call_(
|
|
|
|
|
std::function<int(FFICallback, void*)> f,
|
|
|
|
|
std::chrono::milliseconds timeout) {
|
|
|
|
|
using Bytes = std::vector<std::uint8_t>;
|
2026-05-16 01:08:42 +02:00
|
|
|
auto state = std::make_shared<FFICallState_>();
|
|
|
|
|
auto* cb_ref = new std::shared_ptr<FFICallState_>(state);
|
|
|
|
|
const int ret = f(ffi_cb_, cb_ref);
|
|
|
|
|
if (ret == 2) {
|
|
|
|
|
delete cb_ref;
|
2026-05-29 12:35:49 -03:00
|
|
|
return Result<Bytes>::err("RET_MISSING_CALLBACK (internal error)");
|
2026-05-16 01:08:42 +02:00
|
|
|
}
|
|
|
|
|
std::unique_lock<std::mutex> lock(state->mtx);
|
|
|
|
|
const bool fired = state->cv.wait_for(lock, timeout, [&]{ return state->done; });
|
|
|
|
|
if (!fired)
|
2026-05-29 12:35:49 -03:00
|
|
|
return Result<Bytes>::err("FFI call timed out after " +
|
|
|
|
|
std::to_string(timeout.count()) + "ms");
|
2026-05-16 01:08:42 +02:00
|
|
|
if (!state->ok)
|
2026-05-29 12:35:49 -03:00
|
|
|
return Result<Bytes>::err(state->err);
|
|
|
|
|
return Result<Bytes>::ok(std::move(state->bytes));
|
2026-05-16 01:08:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // anonymous namespace
|
2026-05-26 09:18:12 -03:00
|
|
|
|
|
|
|
|
#endif // NIM_FFI_SYNC_CALL_HELPER_HPP_INCLUDED
|