Timeout interruption limits during FFI execution
0 reputation · 23 Jun 2024, 06:20 UTC
0 reputation · 23 Jun 2024, 06:20 UTC
Racket provides the timeout function to limit the execution time of expressions, returning #f if the specified millisecond threshold is exceeded. This mechanism relies on the runtime's ability to deliver an interruption signal to the executing thread.
A known constraint involves the interaction between these timeouts and the Foreign Function Interface (FFI). When a thread is blocked within a non-interruptible foreign function call, the Racket runtime may be unable to trigger the timeout until the foreign call returns control to the Racket environment.
This creates uncertainty when designing systems that require strict latency guarantees for external library calls.
kill-thread when the target thread is stuck in a foreign function?34275 reputation · 23 Jun 2024, 10:35 UTC
1. Force a timeout during a blocking FFI call? No. The Racket runtime delivers a thread‑interruption flag only at Scheme safe points, which are not reached while a thread is executing C code via the FFI. Therefore a timeout cannot preempt a non‑interruptible foreign call; the flag is only checked after the call returns to Scheme.
2. Expected behavior of kill-thread on a thread stuck in foreign code? kill-thread marks the thread for termination, but the actual stop is deferred until the foreign call completes and control returns to the Racket runtime. Until then the thread continues running the C code and any Racket cleanup is postponed.
The runtime has no mechanism to inject an interruption into arbitrary C code. To obtain hard latency limits you must either:
ffi/unsafe to call the C sleep function for 5 seconds, wrap the call in timeout with a 1 second limit, and observe that the timeout value is returned only after the sleep finishes.sleep, call kill-thread on it, and verify that the thread remains alive until the sleep returns.ffi/unsafe documentation and the release notes for statements about safe points and thread interruption.Does the foreign library you are calling expose a cancellable or non‑blocking variant, or can it be invoked in a way that returns to Scheme more frequently? Knowing this will determine whether process isolation or API redesign is the appropriate solution.
Use comments to ask for clarification. Post a solution as an answer.
5,900 reputation · 23 Jun 2024, 09:38 UTC
Racket’s timeout and kill-thread rely on the runtime’s asynchronous interruption mechanism. An interrupt is delivered to a thread, but it is only acted upon when the thread reaches a safe point that checks for pending interrupts.
When a thread is inside a non‑yielding foreign function (e.g., sleep(5) or a long‑running C loop), the Racket scheduler never regains control. Consequently, the timeout expires or the kill request is queued, yet the thread won’t notice until the foreign call returns and a safe point is reached. At that moment the timeout returns #f or an exn:break is raised.
scheme_blocking_thread or check a cancellation flag.custodian-shutdown-all to clean up resources, but the native code still runs to completion.