5. Execution Model¶
Common contract table of contents
The terminator and Spot execution context combination is owned by 12 HTTP Client §3 and 04 Async Execution Policy §1.1. §5.1 below is a summary to understand the call form. From §5.2, this document defines the non-blocking basis of HTTP transport and the cancellation and timeout boundary.
5.1 The Two Execution Modes And Callback¶
The HTTP client provides one-way submission and response completion.
The exact terminator name is .NET's Async, Kotlin wrapper's
await, Java/C++'s submit. Node distinguishes raw response
submitRaw, typed response/callback async, and one-way submit.
Yield, which returns the shared Spot gate, is provided to the server
HTTP request builder, server request, and Worker call running in an
execution context where returning the gate is allowed — namely,
SpotWide User Spot and Instance Spot. A standalone client has no gate
to return, so it isn't provided.
| Execution Mode | What It Waits For | Spot Execution Queue |
|---|---|---|
| one-way submission | Waits until the HTTP request is submitted at the transport boundary | Keeps the current turn. No normal completion value |
| response completion | Waits until the HTTP response arrives | Keeps the current turn |
Callback is a separate completion path. Used by a caller that doesn't use an awaitable, and the completion callback enters as a new turn of the Spot execution queue (framework 12 §3).
Submit returns the language's standard async value, and doesn't occupy the caller's thread/event loop while waiting on the network.
| Language | Async Return Type | Non-Blocking Basis |
|---|---|---|
| cpp | task_t<T> (co_await) |
Offloaded to the execute scheduler when .coroutines() is active |
| dotnet | ValueTask<T> |
SocketsHttpHandler epoll/IOCP |
| java | CompletionStage<T> |
java.net.http NIO selector |
| kotlin | suspend function |
java runtime + CompletionStage.await() bridge |
| node | Promise<T> |
undici libuv |
The terminator name follows framework convention. .NET uses
Async(...), Kotlin wrapper uses await(...), Java/C++ use
submit(...). Node's HTTP typed response and callback keep async(...)
to avoid a TypeScript inheritance signature conflict, and raw response
uses submitRaw() (04 §2).
5.2 External HTTP Wait And The Spot Execution Queue¶
If another work item and timer of the same Spot must progress while
waiting for an external API, the gate must be returned. The server HTTP
request builder directly provides Yield in that context.
If other work must also proceed while waiting for the response, wrap it
in an I/O Worker and finish with the Worker call's Yield. The example
below is that form.
var profile = await Context
.RunIoWorker(async workerCancellation =>
await http.Get($"/players/{id}").Fetch<Profile>(workerCancellation))
.Yield(ct);
- Don't assume Spot state stays the same across a Worker
Yield. Another callback can change state while waiting. - The response completion terminator keeps the turn. If Spot state must be handled continuously before and after the async wait, use this terminator.
5.3 The Turn Seam — Injecting An Execution Scheduler¶
01 Scope And Architecture §1.3 owns the HTTP client's Framework contract dependency and deliverable boundary. Spot turn integration and completion scheduler injection follow HTTP Client §3.2.
- The HTTP client puts an execution scheduler injection point as a public contract. The scheduler decides where to resume completion.
- The framework wires in a callback completion scheduler at DI registration. The callback enters as a new turn of the Spot execution queue.
- A standalone-use HTTP request builder doesn't expose
Yield. There's no Spot gate to return.
cpp's coroutines(resume_scheduler) / framework_resume_scheduler_t
is the precedent for this seam.
coroutines()— uses the default scheduler.coroutines(resume_scheduler)— injects the resume location (resumes the continuation on the framework execution queue).coroutines(execute_scheduler, resume_scheduler)— injects both execute and resume.
Caution (a current implementation characteristic, not a contract): the cpp default scheduler uses a single thread shared for execute/resume, so requests are serialized, and blocking-waiting for a different task on the same scheduler from a resumed continuation can deadlock.
5.4 Server Runtime Doesn't Put A Blocking Terminator¶
.NET, Java, Kotlin, and Node's Fetch family directly return the
decoded body but complete asynchronously. Don't wait occupying the
current thread just because the name is Fetch.
- Prohibited: an API that waits for an async result on the current
thread, such as
.result(),.join(),.get(). - If a synchronous wait is needed in a test or CLI, the caller
wraps it with a language idiom (
GetAwaiter().GetResult(),runBlocking,.join()). - Composition uses
co_await/await/thenCompose/ suspend.
C++'s fetch<T>() is a separate convenience API for a blocking client
scenario. It isn't used in a Framework server handler.
5.5 Server Surface And Client Lifetime¶
The client used in a server (Spot handler/channel handler) is injected through DI. Don't build a client with a static factory inside a handler — it loses the connection pool and turn seam.
| Surface | Who Uses It | Terminator |
|---|---|---|
| Static factory | CLI · client scenario | response completion / callback |
| DI-injected client | Spot handler · server code | one-way / response completion / callback |
- Build one client per service and reuse it (pool/keep-alive benefit).
- The builder verb shorthand (one-shot) is a convenience path that
lazy-builds the client at submit and closes it after completion.
Since it pays the transport stack initialization cost per request,
don't use it for repeated/high-load calls. A one-shot request object
can't be resubmitted (resubmission is
InvalidOperation).
5.6 Cancellation¶
- dotnet takes a
CancellationTokenon submit. - Propagation of kotlin coroutine cancellation to the underlying request isn't currently implemented (revision candidate R5).
- cpp/java/node don't expose a per-request cancellation API (bounded only by timeout).