Skip to content

feat(ffi): configurable per-request handler timeout with a finite default (#93)#108

Merged
gmelodie merged 3 commits into
masterfrom
feat/config-request-timeout
Jul 6, 2026
Merged

feat(ffi): configurable per-request handler timeout with a finite default (#93)#108
gmelodie merged 3 commits into
masterfrom
feat/config-request-timeout

Conversation

@gmelodie

@gmelodie gmelodie commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #93.

sendRequestToFFIThread used to hand a foreign caller an unbounded wait, so a wedged or slow handler blocked that caller forever — the liveness watchdog detected the wedge but the in-flight request still hung. This wires a finite, configurable handler-completion timeout so the caller unblocks deterministically.

What changed

Each FFIContext now carries defaultRequestTimeout (5s, ffi/ffi_context.nim). In processRequest (ffi/ffi_thread.nim) the handler is raced against its deadline via reportTimeoutIfTripped; on trip the caller is answered with ffi request timed out after <n>ms and a WARN is logged.

The handler is deliberately not cancelled on timeout: a hard-cancel mid-call into the underlying library (Waku/libp2p) can leave it partially applied. We report the timeout and let the handler run to completion. fireCallback (ffi/ffi_thread_request.nim) carries a once-only responded guard so the two response paths (timeout vs. completion) fire the foreign callback exactly once, and freeing stays with handleRes so the request buffer has a single owner.

The deadline is overridable per proc with a {.ffi: "timeout = <ms>".} spec, parsed like the existing abi = ... spec (meta.nim parseTimeoutSpec/overrideKey, wired through ffi/ffiRaw/ffiCtor in ffi_macro.nim). It is runtime-only — recorded in requestTimeoutsMs at module init and read via the context; codegen ignores it.

Notes / scope

The issue's 4a (submit/accept timeout) is obsolete on the current MPSC ingress: submission is a non-blocking enqueue with no accept-ack, so there is nothing to time out on submit. This PR implements 4b, the high-value half.

The timeout race sits inside the same try as the handler await, preserving the existing "handleRes must still run" cancellation-safety invariant. Already-completed handlers (e.g. sync bodies) skip arming a timer to keep the per-request cost off the hot path; a shared per-context timer to eliminate it entirely for async handlers is a possible follow-up. Hard-cancel-on-timeout is left as an opt-in follow-up per the issue's cancellation-safety note — a permanently-wedged handler still unblocks its caller but does not reclaim its own request buffer until it finishes.

Tests

  • tests/unit/test_ffi_context.nim: a handler past its per-proc deadline yields a timeout err and the callback fires exactly once (completion does not re-fire); a handler finishing under its deadline returns normally.
  • tests/unit/test_abi_format.nim: parseTimeoutSpec/overrideKey parsing (valid, malformed, non-positive), and that a timeout override is recorded in requestTimeoutsMs while a plain proc has no entry.

Verified with the host nim-2.2.6 toolchain (nim check + compile-run of the unit suites); all unit tests check clean on orc and refc, and nph is idempotent on the touched files.

@gmelodie
gmelodie requested a review from Ivansete-status July 1, 2026 19:59
@gmelodie gmelodie self-assigned this Jul 1, 2026
@gmelodie
gmelodie marked this pull request as ready for review July 1, 2026 20:00

@Ivansete-status Ivansete-status left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks for it! 🙌
Just added some nitpicks that I hope you find useful.

Comment thread ffi/codegen/meta.nim Outdated
Comment thread ffi/codegen/meta.nim Outdated
requireLibraryDeclared("`.ffiRaw.`")
let prc = args[^1]
gateABIFormat(resolveABIFormat(args[0 ..^ 2]), "`.ffiRaw.` proc")
let (rawAbiFormat, rawTimeoutMs) = resolveFFISpecs(args[0 ..^ 2])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe needed to trim?

Comment thread ffi/internal/ffi_macro.nim Outdated
Comment thread ffi/ffi_context.nim Outdated
Comment thread ffi/ffi_context.nim Outdated
Comment thread ffi/ffi_thread_request.nim Outdated
Comment thread ffi/ffi_thread_request.nim Outdated
Comment thread ffi/ffi_thread_request.nim Outdated
@gmelodie
gmelodie force-pushed the feat/config-request-timeout branch from 57936ad to 1c66cd0 Compare July 6, 2026 13:53
@gmelodie
gmelodie merged commit 8dcdcdc into master Jul 6, 2026
109 checks passed
@gmelodie
gmelodie deleted the feat/config-request-timeout branch July 6, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a configurable request timeout with a finite default

2 participants