mod cancel
srcCancel token plumbed from the reactor into request handlers.
A handler runs to completion regardless of client behaviour. If the
client TCP-disconnects mid-handler, the handler has no signal until
the next _send returns EPIPE. By then the handler has already
done its expensive work (DB query, downstream HTTP fan-out, JSON
serialisation).
the reactor allocates one CancelCell per
connection (heap-allocated Int). Cancel is a thin handle the
reactor passes to a CancelHandler.serve(req, cancel); the handler
polls cancel.cancelled() between expensive steps and short-circuits
when True.
Cancellation is cooperative, not preemptive — Mojo can't preempt synchronous code, and synchronous preemption would defeat the reactor's per-thread invariant anyway. The contract is: the handler checks the flag at boundaries it owns; the reactor sets it at one of:
CancelReason.PEER_CLOSED—recv == 0(peer FIN) observed before the response was queued.CancelReason.TIMEOUT— a per-request, per-handler, or per-body-read deadline fired.CancelReason.SHUTDOWN—HttpServer.drain(timeout_ms)was called.
The default-initialised Cancel returned by Cancel.never() is a
sentinel that never fires; tests and synthetic CancelHandler calls
that don't have a real reactor cell behind them use it.
Implementation
A CancelCell heap-allocates a single Int and owns its
lifetime. Cancel carries the cell's address as an Int and
rebuilds a fresh UnsafePointer[Int, MutExternalOrigin] per access
— the same pattern the multicore Scheduler uses for the
stopping flag, and the only one that survives Mojo's current
(current Mojo nightly) origin / aliasing model when passing the
cancel handle across function-call boundaries:
- Storing a typed
UnsafePointer[Int, MutExternalOrigin]as a struct field inCancelproduces stale reads after the struct is passed through a function (verified empirically: reads at a numerically-correct address returned the pointer struct size,8, instead of the live byte). - An inline
UInt8cell on the owning struct (instead of heap) does not have a stable address across the synchronous call chain the reactor builds, again per direct testing.
The combination here — heap allocation for cell stability + Int
address in Cancel for transport stability — is the only one that
passes the test suite and the
cell.flip(reason); h.serve(req^, cell.handle()) round-trip.
Why Int instead of UInt8 for the cell value: a byte-sized
heap cell hit the same aliasing failure as the typed-pointer field,
suggesting Mojo's aliasing model treats sub-word-aligned loads
through MutExternalOrigin differently than full-word loads. A
machine word avoids the path the bug appeared to depend on. The
cell is one cache line per connection — acceptable cost for the
cancel infrastructure.
Structs
| struct CancelReason | Reason the cancel cell was flipped. |
| struct CancelCell | Heap-allocated per-connection cancel cell. |
| struct Cancel | A handle to a per-request cancel cell owned by the reactor. |
Structs
struct CancelReason §
struct CancelReason
Reason the cancel cell was flipped.
Stored in the cell as an Int; NONE (zero) means "still
live." The handler can branch on the reason via
cancel.reason() == CancelReason.X when it wants to log or
take a different short-circuit path depending on why it was
cancelled (for example, returning a 503 on shutdown vs. a 408
on timeout).
struct CancelCell §
struct CancelCell
Heap-allocated per-connection cancel cell.
Owned by the reactor for the lifetime of a connection (the
reactor keeps one of these on each ConnHandle). Cancel
handles point at this cell. Reset to CancelReason.NONE
between pipelined requests so a peer-FIN on one request doesn't
bleed into the next.
Methods
| fn __init__ | Allocate a fresh cell initialised to ``NONE``. |
| fn __del__ | |
| fn flip | Set the cell's reason. |
| fn reset | Reset the cell to ``NONE``. |
| fn handle | Hand out a ``Cancel`` value bound to this cell. |
fn __init__ static §
__init__(out self)
Allocate a fresh cell initialised to ``NONE``.
Args
| self out | Self |
Returns
| Self |
Raises
May raise an exception.
struct Cancel §
struct Cancel
A handle to a per-request cancel cell owned by the reactor.
Passed to CancelHandler.serve(req, cancel) by the reactor.
Value-copy semantics: copies share the underlying cell.
A handler that ignores cancellation is correct (the request shape is unchanged), but the reactor still flips the cell and the body the handler returns will be the result of work the reactor knew the client had hung up on.
Example:
from flare.http import CancelHandler, Cancel, Request, Response, ok
@fieldwise_init
struct SlowHandler(CancelHandler, Copyable, Movable):
fn serve(self, req: Request, cancel: Cancel) raises -> Response:
for i in range(100):
if cancel.cancelled():
return ok("partial: " + String(i))
# ... one expensive step ...
return ok("done")
Methods
| fn __init__ | |
| fn never | Return a sentinel ``Cancel`` whose ``cancelled()`` is always ``False``. |
| fn cancelled | Return True once the cell is non-zero. |
| fn reason | Return the reason code currently in the cell. |
fn never static §
never() -> Self
Return a sentinel ``Cancel`` whose ``cancelled()`` is always ``False``.
Returns
| Self |