Add cancellation and deadline support to Session.process_request - #994
Add cancellation and deadline support to Session.process_request#994bmehta001 wants to merge 9 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds cross-language cancellation and deadline support for synchronous inference, including engine-level interruption and teardown handling.
Changes:
- Adds native session cancellation, request deadlines, and timeout errors.
- Exposes cancellation/timeouts through C++, Python, C#, and JavaScript.
- Adds chat cancellation and deadline regression tests.
Reviewed changes
Copilot reviewed 37 out of 37 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
sdk_v2/python/src/foundry_local_sdk/session.py |
Adds timeout, cancellation, and teardown signaling. |
sdk_v2/python/src/foundry_local_sdk/request.py |
Adds request timeout configuration. |
sdk_v2/python/src/foundry_local_sdk/_native/build_cffi.py |
Extends Python C ABI definitions. |
sdk_v2/js/src/session.ts |
Adds AbortSignal cancellation and session cancellation. |
sdk_v2/js/src/request.ts |
Adds request deadlines. |
sdk_v2/js/src/detail/native.ts |
Extends native TypeScript interfaces. |
sdk_v2/js/src/detail/errors.ts |
Adds the timeout error code. |
sdk_v2/js/native/src/session.h |
Declares native cancellation methods. |
sdk_v2/js/native/src/session.cc |
Implements native session cancellation bindings. |
sdk_v2/js/native/src/request.h |
Declares native timeout support. |
sdk_v2/js/native/src/request.cc |
Implements native timeout binding. |
sdk_v2/cs/src/Session.cs |
Adds token/session cancellation and teardown signaling. |
sdk_v2/cs/src/Request.cs |
Adds TimeSpan deadlines. |
sdk_v2/cs/src/Detail/NativeMethods.cs |
Extends C# ABI declarations. |
sdk_v2/cs/src/Detail/FoundryLocalApi.cs |
Maps timeout errors and session cancellation. |
sdk_v2/cpp/test/internal_api/chat/chat_session_test.cc |
Adds cancellation and deadline tests. |
sdk_v2/cpp/src/inferencing/session/session.h |
Defines session cancellation state and watchdog support. |
sdk_v2/cpp/src/inferencing/session/session.cc |
Implements cancellation, tracking, and deadlines. |
sdk_v2/cpp/src/inferencing/session/session_manager.h |
Documents process-wide cancellation. |
sdk_v2/cpp/src/inferencing/session/session_manager.cc |
Cancels sessions during shutdown. |
sdk_v2/cpp/src/inferencing/session/request.h |
Implements reusable request deadlines. |
sdk_v2/cpp/src/inferencing/session/oga_generator_cancellable.h |
Declares the OGA cancellation adapter. |
sdk_v2/cpp/src/inferencing/session/oga_generator_cancellable.cc |
Implements engine termination. |
sdk_v2/cpp/src/inferencing/session/live_session_registry.h |
Declares live-session tracking. |
sdk_v2/cpp/src/inferencing/session/live_session_registry.cc |
Implements the live-session registry. |
sdk_v2/cpp/src/inferencing/session/cancellable.h |
Defines the cancellation interface. |
sdk_v2/cpp/src/inferencing/generative/embeddings/embeddings_session.h |
Adds request-aware embedding generation. |
sdk_v2/cpp/src/inferencing/generative/embeddings/embeddings_session.cc |
Adds embedding cancellation checks. |
sdk_v2/cpp/src/inferencing/generative/chat/chat_session.cc |
Publishes chat generators for cancellation. |
sdk_v2/cpp/src/inferencing/generative/chat/chat_generator.h |
Makes chat generators cancellable. |
sdk_v2/cpp/src/inferencing/generative/audio/audio_session.cc |
Adds cancellation throughout audio generation. |
sdk_v2/cpp/src/inferencing/generative/audio/audio_generator.h |
Makes audio generators cancellable. |
sdk_v2/cpp/src/c_api.cc |
Implements new C ABI operations. |
sdk_v2/cpp/include/foundry_local/foundry_local_cpp.inline.h |
Implements C++ wrapper methods. |
sdk_v2/cpp/include/foundry_local/foundry_local_cpp.h |
Exposes C++ cancellation and deadlines. |
sdk_v2/cpp/include/foundry_local/foundry_local_c.h |
Extends the public C ABI. |
sdk_v2/cpp/CMakeLists.txt |
Builds the new cancellation sources. |
Suppressed comments (2)
sdk_v2/cpp/src/inferencing/generative/audio/audio_session.cc:604
- This second raw OGA decode loop has the same termination-exception gap:
Session::Cancel()now terminates the generator duringGenerateNextToken(), but this helper does not catch the expected runtime error. A Nemotron cancellation or deadline can therefore bypass normal cancellation/timeout handling. Handle termination whenoriginal_request.ShouldStop()is true and rethrow unrelated engine failures.
while (!generator.IsDone() && !generator.IsSessionTerminated() && !original_request.ShouldStop()) {
sdk_v2/js/src/session.ts:275
Request_Cancelonly sets the request flag; it never invokes the active generator'sCancel(). Consequently this AbortSignal still waits for an in-progress prefill/decode call and cannot interrupt a non-terminating compute as documented. Moreover, the native cancellation path returns aFINISH_NONEresponse rather thanFOUNDRY_LOCAL_ERROR_OPERATION_CANCELLED, so this promise resolves instead of rejecting. Associate request cancellation with its active generator and define the abort rejection before exposing this option.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Session.process_request() previously had no way to time out or be cancelled outside the streaming path, so a non-terminating non-streaming generation permanently pinned the session refcount. This caused Model.Unload() to fail with '1 session(s) still using it', FoundryLocalManager.close() to exceed its drain deadline, and process teardown to intermittently crash. C++ core: - Add ICancellable interface and OgaGeneratorCancellable adapter so Session can interrupt ORT GenAI mid-compute (SetRuntimeOption(terminate_session)), not just between token boundaries. - Add Request::SetTimeout/ArmDeadline/ShouldStop for a re-armable wall-clock deadline that applies to streaming and non-streaming requests alike. - Add Session::Cancel() with active-generator tracking and a deadline watchdog thread; wire it into all chat/audio/embeddings generation loops. - Cancel() now latches request.canceled on every in-flight request (not just published generators) so finish_reason and history rollback are correct even when cancellation lands during prefill. - Add LiveSessionRegistry so SessionManager::CancelAll() can reach every live session, including ones created via the direct API that never took a SessionRegistration. - Add FOUNDRY_LOCAL_ERROR_TIMEOUT and two C ABI vtable entries: Request_SetTimeoutMs, Session_Cancel (appended to preserve ABI ordering). Bindings (C++ wrapper, Python, C#, JS): - Request.SetTimeout()/set_timeout()/setTimeout(), Session.Cancel()/cancel(). - Non-streaming ProcessRequestAsync (C#) and processRequest (JS) now accept a CancellationToken / AbortSignal that genuinely interrupts an in-flight generation, not just prevents scheduling. - Session teardown (Python _close, C# Dispose, JS dispose) now cancels the session itself, covering the non-streaming case that was previously invisible to shutdown. Tests: added ChatSessionTest coverage for timeout enforcement, timeout error code, deadline re-arming across reused requests, mid-flight cross-thread cancellation, cancel-before-request rejection, and cancel-when-idle safety. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86c9e761-c751-48bc-8e73-2db5257c9e88
Prevent deadline enforcement from poisoning reusable chat state, preserve timeout semantics when ORT terminates raw generators, cancel inference before joining web workers, and release JS streaming callbacks on every exit. Files changed: - sdk_v2/cpp/src/inferencing/generative/audio/audio_session.cc - sdk_v2/cpp/src/inferencing/generative/chat/chat_session.cc - sdk_v2/cpp/src/inferencing/generative/chat/chat_session.h - sdk_v2/cpp/src/inferencing/generative/embeddings/embeddings_session.cc - sdk_v2/cpp/src/inferencing/session/oga_generator_cancellable.h - sdk_v2/cpp/src/inferencing/session/session.cc - sdk_v2/cpp/src/inferencing/session/session.h - sdk_v2/cpp/src/inferencing/session/session_manager.cc - sdk_v2/cpp/src/manager.cc - sdk_v2/cpp/src/service/audio_transcriptions_handler.cc - sdk_v2/cpp/src/service/chat_completions_handler.cc - sdk_v2/cpp/src/service/responses_handler.cc - sdk_v2/cpp/test/CMakeLists.txt - sdk_v2/cpp/test/internal_api/chat/chat_session_test.cc - sdk_v2/cpp/test/internal_api/oga_generator_cancellable_test.cc - sdk_v2/cpp/test/internal_api/session_manager_test.cc - sdk_v2/js/native/src/session.cc - sdk_v2/js/test/streaming.test.ts Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 16cc8b6f-2bc4-4469-bf59-f21439569349
6b1dab0 to
3874896
Compare
Replace session-wide cancellation bookkeeping with a small per-call state so request cancellation interrupts the correct generator, queued timeout includes admission wait, and concurrent embedding requests remain isolated. Keep terminal Session.Cancel semantics without adding new API surface. - Cancel active generators safely without retaining stale pointers - Wake queued chat/audio requests on cancel or timeout - Isolate distinct embedding request cancellation - Preserve sequential request reuse and reject invalid timeout ranges - Add focused model-free regression coverage Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 16cc8b6f-2bc4-4469-bf59-f21439569349
# Conflicts: # sdk_v2/cpp/src/inferencing/session/session.cc # sdk_v2/cpp/src/inferencing/session/session.h
Make live-session shutdown cancellation lifetime-safe, keep JS and Python session handles alive through native work, and align cancellation outcomes across bindings. Replace timing-sensitive timeout tests and the old streaming-audio FINISH_NONE expectation with the new error contract. - Store weak SessionControl references instead of raw Session pointers - Defer JS and Python native release until active calls finish - Reject invalid binding timeout values and pre-aborted JS requests - Make timeout tests deterministic across CI machines - Simplify cancellation naming, comments, and raw OGA adapter wiring Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 16cc8b6f-2bc4-4469-bf59-f21439569349
Ensure CancellationToken cancellation completes with the caller token, release raced native responses, and keep Request and Session handles alive until all native processing exits. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 16cc8b6f-2bc4-4469-bf59-f21439569349
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 54 out of 54 changed files in this pull request and generated no new comments.
Suppressed comments (6)
sdk_v2/python/src/foundry_local_sdk/session.py:485
_check_open()releases the lifecycle lock before this native call, so_close()can transition to closing, release the native session, and leave this thread callingSession_CancelwithNoneor a freed pointer.cancel()is explicitly thread-safe and can race teardown; hold an active-call lease around the native call just likeprocess_request()does.
sdk_v2/python/src/foundry_local_sdk/request.py:138- A positive timeout below one millisecond is truncated to
0, which disables the deadline even though the API documents that onlyNoneand non-positive values disable it. Round positive durations up to the next millisecond so a requested deadline is never silently removed.
sdk_v2/cs/src/Request.cs:148 - A positive
TimeSpanshorter than one millisecond casts to0, silently disabling the timeout despite the contract saying only zero or negative values disable it. Round positive durations up to one millisecond rather than truncating them.
sdk_v2/python/src/foundry_local_sdk/session.py:480 - This documentation omits the method's terminal behavior: native
Session_Cancelpermanently marks the session cancelled, so even an idle call causes every later processing call to fail with invalid usage. State that explicitly; describing only interruption and saying it is safe while idle can lead callers to expect the session remains reusable.
sdk_v2/cs/src/Session.cs:226 - This public contract does not mention that
Cancel()is terminal. The native implementation permanently cancels the session, and subsequent processing calls fail with invalid usage even whenCancel()was invoked while idle. Document that behavior so callers do not expect this to be a reusable, current-operation-only cancellation.
sdk_v2/js/src/session.ts:308 - The documentation presents this as interruption of current work, but native
Session::Cancel()is terminal: calling it while idle still makes all later processing calls fail withFlErrorCode.InvalidUsage. Document the permanent state transition so consumers do not attempt to reuse the session.
Document terminal session cancellation, preserve positive sub-millisecond timeouts, and hold Python/C# native-call leases during cancellation and timeout updates. Make streaming-audio cancellation wait for active processing and consolidate redundant model timeout tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 16cc8b6f-2bc4-4469-bf59-f21439569349
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 54 out of 54 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
sdk_v2/js/src/session.ts:285
- This listener has the same queueing race as streaming: an abort can occur before the async worker attaches the request's invocation state, and native
Request.Cancel()then intentionally does nothing. Since the listener runs only once, the aborted operation can consume the entire generation before line 289 merely converts its successful result toAbortError. Retry until settlement or have the addon latch cancellation for the queued worker.
sdk_v2/js/src/session.ts:166 Request.Cancel()is now an idle no-op, so this one-shot abort can race the queued native worker: if the signal fires afterprocessStreamingRequestreturns but beforeBeginInvocationattaches cancellation state, cancellation is lost and the full inference still runs before JavaScript rejects it. Keep retrying cancellation until the native promise settles, or add a pending-cancellation handshake in the addon.
This issue also appears on line 285 of the same file.
sdk_v2/cs/src/Session.cs:356
- This catch wraps native
TimeoutExceptionand directRequest.Cancel()'sOperationCanceledExceptioninFoundryLocalExceptionwhenever the linked token itself was not canceled. That makes streaming expose different error types from non-streaming and hides the newly added timeout result. Preserve these two mapped exception types before applying the generic streaming wrapper.
Destroy chat generator state before releasing its model, make audio generator cancellation single-shot, preserve typed C# streaming errors, and make Python streaming call leases safe across thread startup. Drain streaming callbacks before finalizing audio and chat outcomes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 16cc8b6f-2bc4-4469-bf59-f21439569349
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 54 out of 54 changed files in this pull request and generated no new comments.
Suppressed comments (2)
sdk_v2/cpp/src/inferencing/session/live_session_registry.cc:18
- There is still a shutdown admission race here: a
Session::Createthat passesIsShutdownRequested()immediately before shutdown can callAddafterCancelAll()has already snapshotted the registry, so its control is never terminated and the session can keep running after shutdown. Make registry shutdown a latched operation synchronized withAdd(for example, aTerminateAllthat marks the registry terminal under this mutex, with later additions rejected or immediately terminated).
void LiveSessionRegistry::Add(const std::shared_ptr<SessionControl>& control) {
std::lock_guard<std::mutex> lock(mutex_);
controls_.emplace_back(control);
sdk_v2/js/native/src/session.cc:394
CancelOncatchesfoundry_local::Erroras a genericstd::exception, so failures lose the stableFoundryLocalErrorname andcodethat every other addon entry exposes. Route this call throughCallCheckedso callers can reliably distinguish native error codes.
Restore concise explanations for completion ordering, request reuse, listener cleanup, native handle lifetime, direct-session shutdown cancellation, and the raw OGA adapter. Keep stale and explicitly removed comments deleted. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 16cc8b6f-2bc4-4469-bf59-f21439569349
Summary
Adds cancellation and timeout support for synchronous inference without introducing a separate operation API.
Request.Cancel()stops the active invocation and interrupts its ORT GenAI generator.ProcessRequestcall and include chat/audio admission wait.Session.Cancel()permanently cancels all active and queued calls on that session.Results
FOUNDRY_LOCAL_ERROR_OPERATION_CANCELLEDFOUNDRY_LOCAL_ERROR_TIMEOUTINVALID_USAGEFINISH_NONEbehaviorCancelled or timed-out chat turns are not committed to history. A generator stopped through
terminate_sessionis discarded and rebuilt from committed history on the next call. Embeddings responses remain all-or-nothing.Assumptions
Validation