Skip to main content

Module call

Module call 

Source
Expand description

One way to ask a service for something, and get an honest answer.

§Why this exists

The SDK offers twelve request_with_reply* functions across three outcome enums. They are all correct; the problem is which one is shortest to type. request_with_reply_deadline returns Option<Message>, so “the request never left” and “the deadline passed” arrive as the same None - and those two demand opposite responses. Services that needed the distinction used to reach for the longer _call_err variant to get it back; since every service moved onto this library (2026-10-09), none calls _call_err directly - they call request_within, which rides it.

This module offers ONE way to ask, in four shapes: request and request_within (the default, and the one to reach for), request_once (the same with no retry, for a caller that owns its own recovery) and request_within_notice (for a person waiting at a prompt). The default keeps the distinction and does the one retry that is provably safe, so that the easy path and the correct path are the same path.

§What it does on your behalf, and what it refuses to

  send ──▶ Reply          you get the message
       │
       ├─▶ SendFailed ──▶ REACQUIRE the peer by name, send ONCE more
       │                  (§14.3: a cap to a restarted service is stale forever,
       │                   and the obligation to notice is the client's)
       │                     └─▶ still failing: Err(Unreachable)  nothing happened
       │
       ├─▶ QueueFull ────▶ Err(Busy)            nothing happened; pace yourself
       │
       ├─▶ ReplyDead ────▶ Err(PeerDied)        IT MAY HAVE HAPPENED; the peer is gone
       │
       └─▶ Timeout ──────▶ Err(OutcomeUnknown)  IT MAY HAVE HAPPENED

It never retries a timeout, or a peer that died holding the request. That is not caution, it is correctness: a slow service is a live service, and re-sending a delete to one is not a retry, it is a second delete whose failure looks like success. ReplyDead says the request was DELIVERED (CLAUDE.md 8.6), so it is the same case with a dead peer instead of a slow one.

This diagram is request_within. request_within_notice with a callback follows it with two differences - a peer that died holding the request reads as OutcomeUnknown, and a person can press q - which its own documentation explains.

§ONE OF THESE IS SAFE FOR A SERVING CALLER, AND THE OTHER IS NOT

Read this before picking. The difference is not ergonomic, it decides whether a task that also SERVES clients can lose one of their requests (CLAUDE.md 8.2).

  • request_within rides CallDeadline. The kernel matches the reply to this call’s own one-shot reply capability and leaves every other message queued. Safe for a serving task.
  • request_within_notice rides the SDK’s _qhint variant, which DRAINS the endpoint before sending (while self.try_recv().is_some() {}) and then waits with a plain timed receive, taking whatever lands next. NOT safe for a serving task: a client request arriving mid-wait is consumed and dropped.

That is deliberate rather than an oversight, and the SDK says so where it lives: _qhint interleaves ON PURPOSE so it can notice a q keypress while waiting, and making it dequeue only the reply would delete that. The honest fix is a bounded stash, which is real work and is not done - so the limitation is recorded here (26.7) instead of being implied away.

It reaches further than this module. Every Fs::call goes through request_within_notice, and so does every Net call, so a handle built with Fs::with_notice or Net::with_notice is on that path too. Use the plain constructors in a service that serves; with_notice is for a foreground command where a person is waiting and may want to press q.

§Authority

Every call takes &ServiceContext. There is no global and no ambient handle. When this task holds no send capability for peer, request_within asks the kernel’s name directory for one (AcquireSendCap), exactly as it does for a stale one - and the kernel grants that only to a task whose spawn request declared peer as a send peer, or that holds ACQUIRE_ANY (the shell, the probes). Anyone else gets Unreachable. So this library widens nothing, but it is not true that it never acquires: a task holding ACQUIRE_ANY can reach any named service through it.

Constants§

DEFAULT_SECS
How long to wait before calling an answer late, when the caller does not say.
NOTICE_AFTER_SECS
How long a wait must last before the caller is told it is lasting.

Traits§

Watch
A COMMAND’s watch over its waits, lent by the program the operator is typing at (the shell).

Functions§

request
Ask peer for something and wait up to DEFAULT_SECS for the answer.
request_once
request_within with NO retry: one send, one bounded wait, and the reason it failed.
request_watched
A bounded request under a command’s Watch: request_within when the watch is not active, and otherwise a request that shows the command’s [q] quit and honours it.
request_watched_sifted
request_watched that takes only the reply mine accepts, and drops any other while it waits. When the watch is not active it is request_within, which does not sift.
request_within
Ask peer for something and wait up to secs for the answer.
request_within_notice
A bounded request with a callback fired once if the wait drags on - and, with a callback, NOT the same machinery as request_within.