@@ -91,6 +91,48 @@ sequenceDiagram
9191 N2-->>N3: value (passive copy)
9292```
9393
94+ ## The async lookup
95+
96+ The registrar exports ` lookup() ` as an asynchronous command, so the script
97+ form is just:
98+
99+ ```
100+ async(lookup("location"), lookup_resume);
101+ ```
102+
103+ What happens underneath:
104+
105+ * If a live local record answers the request — the common case once a node
106+ has converged — the lookup completes inline: no suspension, no extra
107+ cost over the plain synchronous ` lookup() ` .
108+ * On a miss, usrloc starts a cluster pull (owner-hinted when an expired
109+ local copy names the last owner) and hands the async framework the
110+ pull's ** eventfd** . The SIP transaction suspends; ** no worker is held** .
111+ The eventfd exists per pull slot and is created before the fork, which
112+ is what lets the reply land in whichever process the transport picked
113+ and still wake the one that asked.
114+ * When the answer (or the ` pull_timeout_ms ` deadline) fires, a worker
115+ resumes the transaction: it collects the pull, absorbs the record into
116+ local memory, and runs the normal lookup. The resume route replies —
117+ guard with ` t_check_trans() ` as in the example above.
118+ * Everything that cannot suspend falls back to the synchronous path
119+ transparently — other cluster modes, branch-AoR lookups, a pull that
120+ cannot start — so the script never needs a second code path.
121+
122+ Why it matters: a * blocking* pull occupies a worker for up to
123+ ` pull_timeout_ms ` , and pull replies themselves need a worker to be
124+ processed. Under a miss storm every worker can end up blocked in a wait
125+ whose answer is stuck behind it in the queue — the classic reply-starvation
126+ collapse. The async form removes that coupling entirely; combined with the
127+ rule that ** REGISTER never pulls** (a registration is stored locally and
128+ never consults the cluster), the bench filled 1M contacts at 5,000
129+ REGISTER/s with pulls enabled everywhere and lost nothing.
130+
131+ One deliberate exception: the held-only * forced re-ask* (previous section)
132+ runs inside the resuming worker, blocking it for at most one more
133+ ` pull_timeout_ms ` — a bounded price paid only when a record's owner is
134+ gone, against a holder that proved alive milliseconds earlier.
135+
94136## Minimal configuration
95137
96138```
0 commit comments