Repository navigation
A process's threads have their requests to a directory in flight at once - #1272
Conversation
Review of #1272 at b38ca25 (on
|
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 1 responseFixed at ab93cc9, which also rebases onto #1258's head 695146e. Evidence: B1 and B2, fixed by the lock, as the orchestrator decided. Every slot's request (its link, tag and state: idle, waiting, or filed) and the receive turn now live in the table and change only under its lock. The lock is held to read or change them and for nothing else. A caller sleeps on its slot's own futex word, which is moved after every change that concerns it.
B3, taken.
B4, bounded.
B5, done. The record is now S1 taken. The manual names both exceptions: a request that needs the whole window, and one beyond the sixteen a connection carries. S2 declined for now. The controls stay checked patches in the evidence, as this assignment's others are. Making one a S4. With the lock, nothing in the turn can panic. Checks at ab93cc9, all rc 0.
Guest. Run 43107126235 at the head carries the four rows. 43099728928, at b38ca25, was cancelled. |
Review of #1272 round 2 at ab93cc9 (on
|
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before its connection, and the reply bound is derived The receive turn was one per Dir though replies arrive per connection: a caller on a retired but live connection slept while a caller on the current one led, its reply queued and unread, for as long as the current connection's traffic lasted. Each connection now has its own turn: the table keeps the slots whose callers lead, a caller leads when nobody leads its own connection, and a leader or an unsent request's give hands on only within its connection. A request took its connection before its slot, so one waiting for a slot could go out on a connection already retired, into a share its let-go server still writes. The slot now comes first, and connecting again gives it back on failure; a failed request retires its connection before it gives its slot back, so the slot's next request goes out on a newer connection and window. The reply bound claimed a disk minute that bounds only a block listing or open. It is now the block protocol's silence bound, 365 s, and 60 s of room; toyos-blockring, which the SDK cannot name, asserts the bound lies past its own. A file server's reply has no derived worst case, and the record of that gap is opened; the manual says a server silent past its bound while it owes a reply is let go as if it had crashed. loom gains a model in which each connection receives for itself, which deadlocks with one turn per Dir, and one in which a turn handed to a failed send goes on to its connection's waiting caller, which the give's wake alone keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 2 responseFixed at b92c7d2. It now sits on #1258's merge of main (c809599), which brings #1235. Evidence: https://github.com/ToyOSOrg/evidence/tree/7700bf338c950a2a636d036e27cb913366f0a3b0/r2 N1, a turn per connection.
N2, the slot before the connection.
N3, the derived bound.
N4. The phrase now reads "lent a window of its own". Body. "Every wait in the client ends within the bound" is gone, and so is the disk-minute risk. The Risks now name both the reply bound's missing figure and the full-queue blocking send. Checks, all rc 0:
Controls. Each is run model by model:
This round changes no ToyOS-only code, so the images built at round 1 stand. Guest. Run 43112790990 at b92c7d2 carries the four rows. 43107126235 is cancelled. |
Review of #1272 round 3 at b92c7d2 (on
|
| control | models that red |
|---|---|
| filing wake deleted | reply, slot-reuse, connection-end, failed-send |
| leader's hand-on wake deleted | reply, slot-reuse, failed-send |
| give's hand-on wake deleted | failed-send |
fail ignores the link |
fresh-connection, on its own assertion |
round 1's flight.rs (e217642) |
two-connection |
receives ignores the link (mine) |
two-connection, fresh-connection |
waiting ignores the link (mine) |
none (S1) |
The rows that the author also ran match the PR's table.
The author's evidence (7700bf33/r2).
checks/tree.txtrecords e217642 plus a working diff. Its production files match the head; it lacks the eighth model and the record.- The 8-model log has no tree record.
- My runs at the head reproduce both.
ToyOS build.
- The base moved to c809599 since round 1's images.
- No user of this PR's removed API remains at the head: grep finds no
follow_target,TurnstileormutDirbinding. - So the base move cannot break a ToyOS-only caller through this PR's API.
Guest run 43112790990. Still queued, so not read.
SEND BACK
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before its connection, and the reply bound is derived The receive turn was one per Dir though replies arrive per connection: a caller on a retired but live connection slept while a caller on the current one led, its reply queued and unread, for as long as the current connection's traffic lasted. Each connection now has its own turn: the table keeps the slots whose callers lead, a caller leads when nobody leads its own connection, and a leader or an unsent request's give hands on only within its connection. A request took its connection before its slot, so one waiting for a slot could go out on a connection already retired, into a share its let-go server still writes. The slot now comes first, and connecting again gives it back on failure; a failed request retires its connection before it gives its slot back, so the slot's next request goes out on a newer connection and window. The reply bound claimed a disk minute that bounds only a block listing or open. It is now the block protocol's silence bound, 365 s, and 60 s of room; toyos-blockring, which the SDK cannot name, asserts the bound lies past its own. A file server's reply has no derived worst case, and the record of that gap is opened; the manual says a server silent past its bound while it owes a reply is let go as if it had crashed. loom gains a model in which each connection receives for itself, which deadlocks with one turn per Dir, and one in which a turn handed to a failed send goes on to its connection's waiting caller, which the give's wake alone keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its source, and a turn handed on stays on its connection The SDK keeps its own copy of the block protocol's silence bound, which it cannot name from toyos-blockring, a crate that depends on it. The copy is now public, and the file server, which depends on both and whose replies are what wait on block requests, asserts it equals the block crate's; blockring no longer names the file client. The doc at the reply bound says so in a line. loom gains the model in which a turn handed on ignoring its connection deadlocks: two callers on one connection beside a caller leading another, whose server answers last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 3 responseFixed at 04ced8d, rebased onto #1258's round 6 (66de9f9). Evidence: https://github.com/ToyOSOrg/evidence/tree/d146278ec430a6d50d07ba7f1526121be5f33d9e/r3 R1.
S1, taken. The new model is
Checks at the head, all rc 0 but the file server's clippy:
Controls, model by model:
Guest. Run 43117897753 at 04ced8d carries the four rows. 43112790990 is cancelled. |
Review r4 of #1272 at 04ced8d (on #1258's 66de9f9)Scope. Two things:
Architectural assessmentThe block silence bound has one source.
Size this round.
The rebase changed nothing else
Round 3R1, resolved.
S1, taken.
FindingsNone required. No suggestions. EvidenceMy runs at 04ced8d, in a detached worktree with its own target directory, with
Guest run 43117897753 at 04ced8d: still The author's evidence. LAND. It still lands only after #1258, its base. #1258's round-7 blocker is a comment line in a file this PR does not touch, so fixing it would not change this review. |
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before its connection, and the reply bound is derived The receive turn was one per Dir though replies arrive per connection: a caller on a retired but live connection slept while a caller on the current one led, its reply queued and unread, for as long as the current connection's traffic lasted. Each connection now has its own turn: the table keeps the slots whose callers lead, a caller leads when nobody leads its own connection, and a leader or an unsent request's give hands on only within its connection. A request took its connection before its slot, so one waiting for a slot could go out on a connection already retired, into a share its let-go server still writes. The slot now comes first, and connecting again gives it back on failure; a failed request retires its connection before it gives its slot back, so the slot's next request goes out on a newer connection and window. The reply bound claimed a disk minute that bounds only a block listing or open. It is now the block protocol's silence bound, 365 s, and 60 s of room; toyos-blockring, which the SDK cannot name, asserts the bound lies past its own. A file server's reply has no derived worst case, and the record of that gap is opened; the manual says a server silent past its bound while it owes a reply is let go as if it had crashed. loom gains a model in which each connection receives for itself, which deadlocks with one turn per Dir, and one in which a turn handed to a failed send goes on to its connection's waiting caller, which the give's wake alone keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its source, and a turn handed on stays on its connection The SDK keeps its own copy of the block protocol's silence bound, which it cannot name from toyos-blockring, a crate that depends on it. The copy is now public, and the file server, which depends on both and whose replies are what wait on block requests, asserts it equals the block crate's; blockring no longer names the file client. The doc at the reply bound says so in a line. loom gains the model in which a turn handed on ignoring its connection deadlocks: two callers on one connection beside a caller leading another, whose server answers last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before its connection, and the reply bound is derived The receive turn was one per Dir though replies arrive per connection: a caller on a retired but live connection slept while a caller on the current one led, its reply queued and unread, for as long as the current connection's traffic lasted. Each connection now has its own turn: the table keeps the slots whose callers lead, a caller leads when nobody leads its own connection, and a leader or an unsent request's give hands on only within its connection. A request took its connection before its slot, so one waiting for a slot could go out on a connection already retired, into a share its let-go server still writes. The slot now comes first, and connecting again gives it back on failure; a failed request retires its connection before it gives its slot back, so the slot's next request goes out on a newer connection and window. The reply bound claimed a disk minute that bounds only a block listing or open. It is now the block protocol's silence bound, 365 s, and 60 s of room; toyos-blockring, which the SDK cannot name, asserts the bound lies past its own. A file server's reply has no derived worst case, and the record of that gap is opened; the manual says a server silent past its bound while it owes a reply is let go as if it had crashed. loom gains a model in which each connection receives for itself, which deadlocks with one turn per Dir, and one in which a turn handed to a failed send goes on to its connection's waiting caller, which the give's wake alone keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its source, and a turn handed on stays on its connection The SDK keeps its own copy of the block protocol's silence bound, which it cannot name from toyos-blockring, a crate that depends on it. The copy is now public, and the file server, which depends on both and whose replies are what wait on block requests, asserts it equals the block crate's; blockring no longer names the file client. The doc at the reply bound says so in a line. loom gains the model in which a turn handed on ignoring its connection deadlocks: two callers on one connection beside a caller leading another, whose server answers last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebased onto #1258's merge of main: ba7b635#1258 merged main d1cefba, with #1273, at a2e4dfd. This branch is rebased onto it. Main's changes under this branch met it in three places:
Checks at ba7b635, all rc 0 but the file server's clippy:
Evidence: https://github.com/ToyOSOrg/evidence/tree/05bd1f86dde74c9220af61535ac9888748738a4f/r4 Guest. No run is dispatched here. 43117897753 ran at the old base. With main and #1273 now beneath this branch, it needs a run at this head before it lands; the orchestrator decides when. |
Review r5 of #1272 at ba7b635 (on #1258's a2e4dfd)Scope.
Architectural assessmentThe rebase changed only the three places the author names.
Each resolution keeps both sides' guarantees.
FindingsNone required. No suggestions. For the next rebase. #1258's B2 changes EvidenceThe author's r4 logs (05bd1f86).
Guest.
Metal: none. It is not a landing condition while the benches are down. LAND, once #1258 has landed and run 43143742563's rows pass. |
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before its connection, and the reply bound is derived The receive turn was one per Dir though replies arrive per connection: a caller on a retired but live connection slept while a caller on the current one led, its reply queued and unread, for as long as the current connection's traffic lasted. Each connection now has its own turn: the table keeps the slots whose callers lead, a caller leads when nobody leads its own connection, and a leader or an unsent request's give hands on only within its connection. A request took its connection before its slot, so one waiting for a slot could go out on a connection already retired, into a share its let-go server still writes. The slot now comes first, and connecting again gives it back on failure; a failed request retires its connection before it gives its slot back, so the slot's next request goes out on a newer connection and window. The reply bound claimed a disk minute that bounds only a block listing or open. It is now the block protocol's silence bound, 365 s, and 60 s of room; toyos-blockring, which the SDK cannot name, asserts the bound lies past its own. A file server's reply has no derived worst case, and the record of that gap is opened; the manual says a server silent past its bound while it owes a reply is let go as if it had crashed. loom gains a model in which each connection receives for itself, which deadlocks with one turn per Dir, and one in which a turn handed to a failed send goes on to its connection's waiting caller, which the give's wake alone keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its source, and a turn handed on stays on its connection The SDK keeps its own copy of the block protocol's silence bound, which it cannot name from toyos-blockring, a crate that depends on it. The copy is now public, and the file server, which depends on both and whose replies are what wait on block requests, asserts it equals the block crate's; blockring no longer names the file client. The doc at the reply bound says so in a line. loom gains the model in which a turn handed on ignoring its connection deadlocks: two callers on one connection beside a caller leading another, whose server answers last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rebased onto #1258's round-8 head 3581821 (main 3c5c555 merged there). The range-diff is Checks at the head 6bf86dd, all rc 0: the SDK's tests (214) and two clippy shapes, nine loom models, the file server's tests, and the Guest: run 43147796257 at 6bf86dd runs the four rows and |
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before its connection, and the reply bound is derived The receive turn was one per Dir though replies arrive per connection: a caller on a retired but live connection slept while a caller on the current one led, its reply queued and unread, for as long as the current connection's traffic lasted. Each connection now has its own turn: the table keeps the slots whose callers lead, a caller leads when nobody leads its own connection, and a leader or an unsent request's give hands on only within its connection. A request took its connection before its slot, so one waiting for a slot could go out on a connection already retired, into a share its let-go server still writes. The slot now comes first, and connecting again gives it back on failure; a failed request retires its connection before it gives its slot back, so the slot's next request goes out on a newer connection and window. The reply bound claimed a disk minute that bounds only a block listing or open. It is now the block protocol's silence bound, 365 s, and 60 s of room; toyos-blockring, which the SDK cannot name, asserts the bound lies past its own. A file server's reply has no derived worst case, and the record of that gap is opened; the manual says a server silent past its bound while it owes a reply is let go as if it had crashed. loom gains a model in which each connection receives for itself, which deadlocks with one turn per Dir, and one in which a turn handed to a failed send goes on to its connection's waiting caller, which the give's wake alone keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its source, and a turn handed on stays on its connection The SDK keeps its own copy of the block protocol's silence bound, which it cannot name from toyos-blockring, a crate that depends on it. The copy is now public, and the file server, which depends on both and whose replies are what wait on block requests, asserts it equals the block crate's; blockring no longer names the file client. The doc at the reply bound says so in a line. loom gains the model in which a turn handed on ignoring its connection deadlocks: two callers on one connection beside a caller leading another, whose server answers last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rebased onto #1258 cb8701b (round 9: the stale holder assertion in Guest: 43152649297 at 2e7dd79 runs the four rows and |
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before its connection, and the reply bound is derived The receive turn was one per Dir though replies arrive per connection: a caller on a retired but live connection slept while a caller on the current one led, its reply queued and unread, for as long as the current connection's traffic lasted. Each connection now has its own turn: the table keeps the slots whose callers lead, a caller leads when nobody leads its own connection, and a leader or an unsent request's give hands on only within its connection. A request took its connection before its slot, so one waiting for a slot could go out on a connection already retired, into a share its let-go server still writes. The slot now comes first, and connecting again gives it back on failure; a failed request retires its connection before it gives its slot back, so the slot's next request goes out on a newer connection and window. The reply bound claimed a disk minute that bounds only a block listing or open. It is now the block protocol's silence bound, 365 s, and 60 s of room; toyos-blockring, which the SDK cannot name, asserts the bound lies past its own. A file server's reply has no derived worst case, and the record of that gap is opened; the manual says a server silent past its bound while it owes a reply is let go as if it had crashed. loom gains a model in which each connection receives for itself, which deadlocks with one turn per Dir, and one in which a turn handed to a failed send goes on to its connection's waiting caller, which the give's wake alone keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its source, and a turn handed on stays on its connection The SDK keeps its own copy of the block protocol's silence bound, which it cannot name from toyos-blockring, a crate that depends on it. The copy is now public, and the file server, which depends on both and whose replies are what wait on block requests, asserts it equals the block crate's; blockring no longer names the file client. The doc at the reply bound says so in a line. loom gains the model in which a turn handed on ignoring its connection deadlocks: two callers on one connection beside a caller leading another, whose server answers last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rebased onto main ee15a2e (#1258 merged, beside #1252). #1252 made a directory capability a connector the process holds. The conflicts were
|
Review of #1272's rebase onto #1252 at d88c74fScope: the conflict resolutions with #1252 only. The design (per-connection receive turn, slot before connection, the 425 s reply bound) is settled and not reopened. ArchitectureThe resolutions are the natural composition. #1252 owns where the connection comes from ( What I verified
FindingsB1 — PR body, "Rebased onto main ee15a2e": one build claim is false, and the linked evidence holds two failed runs the body does not mention.
No other findings. The resolutions keep #1252's connector and words and #1272's shared connection, restart path and receive turn, and no check was lost. SEND BACK (B1 is a correction to the PR body, which the orchestrator owns; nothing in the branch needs to change). |
Every request through a directory capability took the process's one turn on it for its whole round trip, though the wire carries 16 tagged requests: a thread asking beside another of its process waited for the other's answers, 2.8x for fstat on the T14 in scratch timers. Now a request takes one of 16 slots, its share of the window and the place its reply is filed, and a tag naming the slot; one larger than a slot takes the whole window, ahead of any slot asked for after it. A caller waiting for its reply takes the receive turn if nobody holds it, receives on its connection and files each other caller's reply in its slot until its own comes, then hands the turn to a caller still waiting. A receive that fails, or a reply naming no request in flight, fails every request on that connection. A connection that ends is retired, its requests in flight finishing or failing with it, and the next request connects again; a file id stays good only on the connection that answered it, now checked against the request's own. Dir's calls take &self, so a capability is one shared Dir and the turnstile goes. A link to follow comes back with its target, copied out of the request's share before the share is given back. kernel-loom runs the in-flight core against loom with a futex that sleeps until woken, so a caller nobody wakes is loom's deadlock. Its first model found one: a reply filed by a plain store raced the turn handed to the same slot, and the owner could read the handing back after its own filing and receive for ever. Every change of a waiting slot's state is now a read-modify-write. osbench gains stat and open measured beside a thread asking stat, which the record owes on metal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry reply wait is bounded The in-flight core handed the receive turn on lock-free, beside the table's lock. A send that failed gave its slot back while the leader could still be filing a failure into it, so a fresh connection's request in that slot failed with the old one; and the turn's hand-on was a store buffering pattern that only the hardware's ordering kept from losing a receiver. Now every slot's request and the turn are read and changed under the table's lock, held for nothing else, and a caller sleeps on its slot's own word, moved after every change that concerns it: a reply or failure filed, or the turn offered to it. A request given back unsent offers the turn on while nobody receives. A leader hands the turn to a caller on its own connection first. Every reply wait is bounded: a server that answers nothing for five minutes while a reply is owed is let go as one that ended, Unanswered (ETIMEDOUT, TimedOut), and every caller waiting on that connection fails with it. Each connection lends its own window, so a server still writing a request it was let go with writes that window, never a newer connection's. The loom shim's futex now returns only once its own word is woken, as the kernel's does; before, any wake woke every sleeper, so no model could miss a wake. The models now cover a slot used again, a failed send beside a fresh connection, and a whole-window request that receives; deleting any one of the three wakes deadlocks one of them, and failing a request on another connection fails the fresh-connection model. The plain-store control is gone: it failed only through loom's partial modification order, which C++ does not allow. The record of the turnstile is now the measurement it owes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before its connection, and the reply bound is derived The receive turn was one per Dir though replies arrive per connection: a caller on a retired but live connection slept while a caller on the current one led, its reply queued and unread, for as long as the current connection's traffic lasted. Each connection now has its own turn: the table keeps the slots whose callers lead, a caller leads when nobody leads its own connection, and a leader or an unsent request's give hands on only within its connection. A request took its connection before its slot, so one waiting for a slot could go out on a connection already retired, into a share its let-go server still writes. The slot now comes first, and connecting again gives it back on failure; a failed request retires its connection before it gives its slot back, so the slot's next request goes out on a newer connection and window. The reply bound claimed a disk minute that bounds only a block listing or open. It is now the block protocol's silence bound, 365 s, and 60 s of room; toyos-blockring, which the SDK cannot name, asserts the bound lies past its own. A file server's reply has no derived worst case, and the record of that gap is opened; the manual says a server silent past its bound while it owes a reply is let go as if it had crashed. loom gains a model in which each connection receives for itself, which deadlocks with one turn per Dir, and one in which a turn handed to a failed send goes on to its connection's waiting caller, which the give's wake alone keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its source, and a turn handed on stays on its connection The SDK keeps its own copy of the block protocol's silence bound, which it cannot name from toyos-blockring, a crate that depends on it. The copy is now public, and the file server, which depends on both and whose replies are what wait on block requests, asserts it equals the block crate's; blockring no longer names the file client. The doc at the reply bound says so in a line. loom gains the model in which a turn handed on ignoring its connection deadlocks: two callers on one connection beside a caller leading another, whose server answers last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rebased onto main 29c5f2d (#1227, #1264, #1280); head 4227aa4.
|
|
Orchestrator verification at 4227aa4, the rebase onto main 29c5f2d (#1227). I read the range-diff against d88c74f. Commits 1, 3 and 4 are The rebase review's B1 is answered in the body:
Landing run 43165795439 at d88c74f, base ee15a2e:
Queueing. |
Problem
Every request through a directory capability took the process's one turn on that capability (a
Turnstile) for its whole round trip, though the wire carries 16 tagged requests. A thread asking beside another thread of its own process waited for the other's answers. In scratch timers on the T14,fstaton /tmp beside such a thread read 21.4 to 21.6 µs p50, against 7.6 alone (2.8x). Beside another process it read 8.4 µs (issues/a-call-beside-another-thread-of-its-process-is-unmeasured-on-metal.md, the record of the measurement now owed).Change
REPLY_BOUND_NSwhile a reply is owed is let go as one that ended.Unanswered, a new client-local error (ETIMEDOUT,TimedOut), and every caller waiting on that connection fails with it.BLOCK_SILENCE_NS, since toyos-blockring depends on it. The file server, which depends on both and whose replies wait on block requests, asserts the copy equals blockring'sSILENCE_BOUND_NS.severity: [hang]inissues/a-file-server-reply-has-no-derived-worst-case.md.Stale.Dir.Dir's calls take&self, so a capability is oneArc<Dir>. The turnstile and its comments go. Callers that bound aDirmutably no longer do: pkg, the supervisor, osbench and five rust-tests binaries.stat.beside_a_threadandopen_close.beside_a_thread: each measured while another thread of the process asksstatas fast as it is answered.Checks
Host, at the head 04ced8d, on #1258's 66de9f9 (main merged, with #1235's block silence bound). Logs: d146278e/r3; earlier rounds' at r2 and r1.
cargo test -p toyos --lib fs::,files::,sync::toyos(all targets, andfd-table), warnings deniedcargo test -p toyos-blockring --liband its clippycargo test --manifest-path system/fileserver/Cargo.toml(it compiles the bound's assertion) and its clippycargo test --manifest-path kernel/loom/Cargo.toml --test fs_flight(9 models)cargo clippy --manifest-path kernel/loom/Cargo.toml --tests -- -D warningstestcasesimages, at round 1; this round changes no ToyOS-only codeThe loom models. They run the SDK's own in-flight file,
sdk/toyos/src/fs/flight.rs, against loom's lock and atomics. The shim's futex sleeps under a lock that each wake takes after its store, and it returns only once its own word is woken, as the kernel's futex does. So a wake the protocol leaves out is a loom deadlock. The nine models:The bound is 3 preemptions for the three-thread models and 2 for the slot-reuse and four-thread ones, which run past minutes at 3.
Controls. Each is a patch applied, run model by model, and restored.
Dir(round 1'sflight.rs): the two-connection model deadlocks.failignoring the connection: the fresh-connection model's request fails with the old connection.The first version's lock-free hand-on. Review found a stale failure filed into a reused slot, and a store-buffering lost receiver that loom cannot see. Both are gone with the lock. An earlier version of this description said the first model "found a real lost receiver". That was wrong: the red came from loom's partial modification order, which C++ forbids, and the plain-store control built on it is dropped.
Not built here. osbench's ToyOS half and the five rust-tests binaries build only in the harness. Each change there drops a
mutfrom aDirbinding now used through&self. Every rust-tests binary type-checks against the ToyOS sysroot at the head (Rebased onto main, below); osbench's ToyOS half is not checked here. Thetestcasesshared-boot guest row builds them all.Rebased onto main. First onto ee15a2e (#1258 merged, beside #1252). #1252 made a directory capability a connector the process holds, where it had been a name in a namespace; the rebase keeps that with this PR's shared connection.
connectmakes theDirand its first connection,reconnectconnects through the held connector, the SDK's capability table keepsArc<Dir>, and the supervisor's sync, pkg and three guest tests drop amut(r8). Then onto 29c5f2d (#1227, #1264, #1280). #1227 replaced libc's and std's tables with oneFailuretable, soError::Unansweredis that table'sTimedOut(libc's ETIMEDOUT, std'sTimedOut); its match is exhaustive, so the compile settles that the arm is there. Commits 1, 3 and 4 are unchanged by that rebase (r9).Checks at the head 4227aa4, each rc 0 (r9): the SDK's tests and two clippy shapes, the nine loom models, the file server's tests, the
testcasesimage (its sysroot, libc and std, and the supervisor), every rust-tests binary type-checked against that sysroot, andcargo check -p pkg --all-targetson the host, where pkg's one change compiles without a warning. Two runs in r8 ended rc 101, neither because of this PR.cargo test -p supervisorcannot build on the host, since the supervisor is a ToyOS program; the image is its compile. The clippy run on the supervisor and pkg stopped at aredundant_cloneinsystem/pkg/src/archive.rs:177, a line this PR does not touch and the same on main.Guest. Run 43165795439 at d88c74f, on main ee15a2e, kept fetchable as branch
fileops-tags-hold-d88c74fa1. This PR's change there differs from its change at the head 4227aa4 only in whereUnanswered's ETIMEDOUT andTimedOutare said, theFailurearm; the rest of the difference is main's. The run coversnvme_disk_boots,the_data_partition_beside_the_stick_is_designated,the_log_lands_on_the_boot_stick,virt_user_mode(aarch64; the SDK transport is shared) andtestcases_members_exit_0, thetestcasesshared boot as a guest row (#1271), whose members put several requests on one connection (a_served_files_writes_land_whole's server writes) and run the export,dup,writableand generation members. Every program's file calls go through this client, so the rows establish the single-thread path, connect and generation. Before the rebase, 43152649297 passed the same five at 2e7dd79 (log); 43117897753 passed the four rows at an earlier head; 43143742563 and 43147796257 were cancelled while queued.Metal. None; the benches are down. The speed-up is not measured. The record owes
osbench_diskon the T14, main before and after, A, B, B, A:stat.beside_a_threadandopen_close.beside_a_threadwithin 1.2x ofstatandopen_closeon both volumes.Risks
testcases_members_exit_0's multi-threaded members: all 242 exited 0 in run 43152649297 before the rebase, under QEMU on two vCPUs, and run 43165795439 runs them at d88c74f. Nothing has run it on metal.issues/a-file-server-reply-has-no-derived-worst-case.md).CLOSEs waits without bound, as it did before this change.🤖 Generated with Claude Code