Repository navigation
A poll belongs to the handle it was made through, and a ring to its one submitter - #1261
Conversation
Review of #1261 at
|
… names the deleted poll files `tests/guest/rust-tests` is outside the workspace and keeps its own lockfile, which still pinned mio at `fe42def`. It is the one ToyOS consumer of mio (`mio_waker_coalesces`), so the selector's generation drop shipped in no ToyOS binary. Its lockfile now pins `4108eff`, as the root lockfile does; `cargo metadata --locked` agrees with both. `system/netstack/src/watches.rs` and `sdk/toyos/tests/transport_loom.rs` cited `kernel/src/inbox/polls.rs`, now `kernel/pure/sched/poll.rs`. `issues/parallel-tests-red-under-other-suites.md` named the source gate and the `close_ends_polls` arm this change deleted. What the gate held is now structural: a close ends only the polls made through the handle it closes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2 of #1261 at
|
… names the deleted poll files `tests/guest/rust-tests` is outside the workspace and keeps its own lockfile, which still pinned mio at `fe42def`. It is the one ToyOS consumer of mio (`mio_waker_coalesces`), so the selector's generation drop shipped in no ToyOS binary. Its lockfile now pins `4108eff`, as the root lockfile does; `cargo metadata --locked` agrees with both. `system/netstack/src/watches.rs` and `sdk/toyos/tests/transport_loom.rs` cited `kernel/src/inbox/polls.rs`, now `kernel/pure/sched/poll.rs`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f897ae4 to
8e8b852
Compare
…s change made `issues/nothing-fails-when-a-devices-release-or-close-stops-answering-its-polls.md` named `WatchRef::cancel_polls`'s `Irq` arm as a claim's close site. This change deleted that arm. A claim's close or move now ends the polls made through its handle, by `ops::end_polls` and `WatchRef::end_polls`'s `Claim` arm. The record now says so. It also says which release still answers a live poll: `tear_down_departed`, because a release after the last close finds none. Its exit is restated for both: a test for each path, red with its call removed. The record's audio claim no longer exists and leaves it. Rebased onto main 45935f4, which deleted `issues/parallel-tests-red-under-other-suites.md`; the deletion is kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Orchestrator: verified 8e8b852 against follow-up r2. The rebase onto 45935f4 keeps main's deletion of |
…e poll-path A/B, recorded while the benches are down The benches are down, so metal is no longer a landing condition. What only metal shows of this change is recorded as owed, each in a record this change already updates: - `issues/nothing-fails-when-a-devices-release-or-close-stops-answering-its-polls.md`: the T14's `testcases`, `testcases-watchdog` and `shared-debug` boots of main, with `poll_belongs_to_its_handle` and `pipe_other_end_gone`; and the osbench A/B of main from before and after this change, over the wake, pipe and IPC rows, by the rule stated before any boot ran. - `issues/mios-selector-arms-no-registration-made-while-another-thread-selects.md`: `mio_waker_coalesces`, built from mio `4108eff`. - `issues/a-claims-holder-lengthens-its-handlers-interrupts-off-walk-by-the-polls-it-arms.md`: the `windowscase` boot, with `mask_windows` and the reshaped herd. Record edits only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne submitter
A ring kept its polls by handle, but an object's watch kept its entries by
nothing. So a close could end every ring's poll on the object or none, and
`close_ends_polls` chose which of the two wrong answers each object kind got.
Closing a duplicate of a pipe end, an event's wait side or an acceptor ended
the other handle's polls in every ring. Closing the handle a `Process`,
`SysCap`, `Job` or claim watch was made through left that watch armed until
its object posted. Two threads could submit to one ring, race its last
completion slot, and park on a ring whose close woke neither.
A poll and its watch entries now carry `Via { table, handle }`: the handle
table it was looked up in, and the handle there. A handle's entry carries one
bit, `polled`. The registration adds the entries in one hold of the table
that reads the handle still there (`poll::register`, through
`HandleTable::while_held`); a handle that left first ends the poll. Every way
out of the table ends the entries made through that handle and no other's:
`remove`, which is a close and an endowment, and a committed `transfer`,
which is a send. A refused move ends nothing. `Watch::end_rings(via)` takes
the matching entries out a few at a time and fires and frees them with the
list lock let go. `close_ends_polls`, its per-kind table and the three list
locks every close took are gone. So is the acceptor's cancel at its last
handle, which no entry can outlive any more. `cancel_rings` stays for a
source that ends under live handles: a claim's release, a function leaving.
A ring has one submitter by type. `inbox_submit` claims the ring with one
exchange and holds a `Submitting` until it returns. A second concurrent submit
is refused with a new ABI error, `Busy`, and touches nothing. The completions
are reached only through `Submitting`, so the room an answer was read for is
the room it is written into. An answer no longer posts the ring's watch, and
`Kept::looking` is gone with the look's hiding of a poll from a second
submitter. The ring's teardown posts its watch after taking the state, so a
submitter parked on it reads the ring gone and returns `NotFound`. `Poller`
loses `unsafe impl Sync`.
The one-shot, the polls, delivery, the park predicate, the registration and
the teardown move from `kernel/src/inbox/` to `kernel/pure/sched/poll.rs`,
which `kernel-loom` already compiles. They get host tests of the transition
table, replacement by key, the room read and written, and the park predicate
over a ring gone. Four new loom models, each with a control that must red it:
a close racing a post (the split `Once`), a close racing a registration
(`register-outside-table`), a replacing watch racing a post
(`admit-keeps-the-replaced`), and a teardown racing a park
(`teardown-posts-nothing`). The two-submitter model and the two
`inbox_answer` cases that staged a second submitter's watch during a look are
deleted with what they modelled.
The mio fork's selector gives each registration a generation, which its
watch's token carries, and its drain drops a completion whose registration is
gone or was made again. So no event is reported for a handle after its
deregister returned. The fork is pinned at that commit.
`poll_belongs_to_its_handle` replaces `inbox_cancel_wakes`, whose claim this
inverts. It shows that a duplicate's close answers nothing on a pipe end and on
an event's wait side, and that the watched handle's own close answers its
parked submitter `NotFound`. It shows that a child watched through a duplicate
is answered at that duplicate's close, while the child lives, and that a
watched handle sent away is answered. And it shows that a second submit is
refused `Busy` and a ring closed under an unbounded submit returns it.
`ring_park_herd` parks its 256 threads each on a ring of its own and wakes
them by events: one ring can no longer hold more than one.
Closes `issues/a-close-of-one-handle-ends-every-rings-poll-on-its-object.md`
and its duplicate `issues/closing-a-pipe-end-ends-every-holders-polls-on-it.md`,
`issues/a-watch-outlives-the-close-of-the-process-handle-it-was-made-through.md`,
`issues/mio-deregister-fd-leaves-a-pending-poll-live.md`,
`issues/closing-a-ring-leaves-its-submitters-parked.md`,
`issues/two-submitters-of-one-ring-race-its-last-completion-slot.md` and
`issues/toyos-poller-is-sync-and-a-watch-moves-the-tail-in-two-steps.md`.
The interrupts-off walk record is narrowed to a claim's handler and renamed.
The mio selector's missed registration during a parked select gets its own
record.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… names the deleted poll files `tests/guest/rust-tests` is outside the workspace and keeps its own lockfile, which still pinned mio at `fe42def`. It is the one ToyOS consumer of mio (`mio_waker_coalesces`), so the selector's generation drop shipped in no ToyOS binary. Its lockfile now pins `4108eff`, as the root lockfile does; `cargo metadata --locked` agrees with both. `system/netstack/src/watches.rs` and `sdk/toyos/tests/transport_loom.rs` cited `kernel/src/inbox/polls.rs`, now `kernel/pure/sched/poll.rs`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s change made `issues/nothing-fails-when-a-devices-release-or-close-stops-answering-its-polls.md` named `WatchRef::cancel_polls`'s `Irq` arm as a claim's close site. This change deleted that arm. A claim's close or move now ends the polls made through its handle, by `ops::end_polls` and `WatchRef::end_polls`'s `Claim` arm. The record now says so. It also says which release still answers a live poll: `tear_down_departed`, because a release after the last close finds none. Its exit is restated for both: a test for each path, red with its call removed. The record's audio claim no longer exists and leaves it. Rebased onto main 45935f4, which deleted `issues/parallel-tests-red-under-other-suites.md`; the deletion is kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e poll-path A/B, recorded while the benches are down The benches are down, so metal is no longer a landing condition. What only metal shows of this change is recorded as owed, each in a record this change already updates: - `issues/nothing-fails-when-a-devices-release-or-close-stops-answering-its-polls.md`: the T14's `testcases`, `testcases-watchdog` and `shared-debug` boots of main, with `poll_belongs_to_its_handle` and `pipe_other_end_gone`; and the osbench A/B of main from before and after this change, over the wake, pipe and IPC rows, by the rule stated before any boot ran. - `issues/mios-selector-arms-no-registration-made-while-another-thread-selects.md`: `mio_waker_coalesces`, built from mio `4108eff`. - `issues/a-claims-holder-lengthens-its-handlers-interrupts-off-walk-by-the-polls-it-arms.md`: the `windowscase` boot, with `mask_windows` and the reshaped herd. Record edits only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o longer uses #1241 deleted the watch's gate model and this change deleted the two-submitter model. Those were `loom_watch`'s two uses of `CPU1`, so after both the import is unused, and `cargo clippy -p kernel-loom --all-targets` refuses it. The rebase's other resolutions are in the commits it replays: `resolve` and `ThisProcess` look up through #1246's `with_handles`, and the watch imports `poll::Via` beside main's `Arc` and `CellLock`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e0eb8f9 to
39f9da6
Compare
Rebase review of #1261 at
|
|
Orchestrator: landing under the benches-down rule. Rebase review LAND at 39f9da6; guest run 43108631616 at that head: success (five rows incl. aarch64 |
…leave the kernel The kernel kept, for every process, the labels its parent put on each endowment, compared three of them (`job`, `self`, `quit`), answered them back through `SYS_ENDOWMENTS`, and held a namespace object that mapped service names to connectors behind three more calls. None of that is the kernel's: it is what one program calls a handle it gave another. Now a spawn moves a list of handles and nothing else. The child's table starts with its handle to itself (`RawHandle::SELF`), its notice (`RawHandle::NOTICE`), then the endowments in the order the spawn listed them (`RawHandle::endowed(i)`); the kernel installs them so and asserts it. What the parent calls each is a table it writes in the child's startup block, which the child's SDK checks once, whole, before it looks one up: a table that is no list of distinct, non-empty UTF-8 names ends the child, said on its `fd:2` where the table still names one. - `SpawnArgs::job` replaces the `job` label: a borrowed handle carrying `WRITE`, resolved once like the place and the pager, so the commit's re-check that the moved handle still named the job goes. - `SYS_CONNECT` (102, `SYS_NAMESPACE_OPEN`'s number) connects through a connector carrying `WRITE`, which a connector is now minted with. One without it may only be passed on. - `SYS_ENDOWMENTS` (99), `SYS_NAMESPACE_BUILD` (101), `SYS_NAMESPACE_NAMES` (170), the namespace object, `SpawnEndow`, `EndowEntry`, the label blob and its per-process charge go; the freed numbers stay unassigned. `MAX_SPAWN_ENDOWMENTS` is 96: the 30 a spawn carried plus the 64 a namespace held, so everything one table and one namespace expressed still fits. The names are bounded by the startup block alone. - `ObjectKind::Namespace` goes and its word 8 stays unused, as `ObjectKind` says a word that goes does: `SysCap` through `DeviceSet` keep 9 to 19, `from_word` looks a word up rather than indexing by it, the SDK's census finds a kind's count by its place in `ALL`, and the SDK's receive test takes word 8 and the word past the last kind as ones no kind has. - The supervisor, whose parent is the kernel, starts holding its system capability, the file server's image, ROOT's and the boot names at the four handles `toyos_abi::boot::supervisor` states. In userland a service is a connector named `svc:<name>` (a row's `receives`, its view's `fs:` directories, minted grants) or `provide:<name>` (one a launching client sent). `endow::service` looks one up and connects; a helper is given a duplicate of each its parent holds, narrowed by `CommandExt::services_only`. Everything else is taken once by name. The supervisor gives a provided connector to a row that does not receive it without `WRITE`; swap's connector arrives with `WRITE` alone, as its one holder only connects. The supervisor adopts its own directory capabilities (`std::os::toyos::fs::adopt_services`), which no child inherits: so a storage row, whose view is empty, now starts holding no file grant, where it inherited the supervisor's whole tree. A connector a peer sent, as a session's launcher and controls are, is typed only where the kernel reported a connector (`ReceivedHandle::typed`), and an endowed one on its parent's word. std's `adopt_services` is `unsafe`, as `adopt_namespace` was: the handles it takes are its caller's word. `SYS_NAMESPACE_OPEN`'s name was the last caller of `SyscallContext::user_str`, since #1238 reads a spawn's path as bytes bounded by `MAX_PATH`. So `user_str` and `MAX_USER_STR` go. `KernelStack::heap` charged every thread two 64 KiB strings and a page for what a call copies in. It now charges, by their types, what a spawn copies in and holds where it may be preempted: - its path, name and build-id at their bounds; - its ranges; - its endowments; - the mappings of the device claims it moves, in a vector sized once at the first claim so it never grows past that. It claims nothing for another call. A block session holds a connector and no service name, so diskserver's two messages for a session's server that broke the protocol or went silent name the session by its partition GUID. #1261's `poll_belongs_to_its_handle` connects through its connector, where it opened a one-entry namespace. Kernel: 55,475 lines in 210 files at main a7ca8ad to 55,117 in 209. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `testcases` boot owed on metal for #1252 now names the four shared-boot members whose rebased forms have only been compiled: #1261's `poll_belongs_to_its_handle`, which connects through its connector where it opened a one-entry namespace, and `messages_are_whole`, `moved_rights` and `mutual_kill`. No guest row runs them until #1271's shared-boot row lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r's slots, as no-holders ones are Main brings #1273: a pager's object slots are made as they are wanted (kernel::slots::Slots), pager_create takes no object count, and the objects whose last holder went are a list through the slots, not a walk over every slot. #1258 had kept its dirtied objects the old way, a flag an entry carries, a count, and a walk in notices. - Slots holds a const number of mark lists, each apart from the others; a removal unmarks a slot on every one. The pager's table has two: NO_HOLDERS, #1273's, and DIRTIED. owe_dirtied marks and unmarks the second, has_notices asks both, notices takes from both, and a release unmarks both. The only walk over every slot left is the pager's end. - An entry keeps #1258's writers and record beside #1273's fields, and add makes it in the slot make_slot charged for. holder_gone counts the holder down and say_no_holders marks the object only once no holder's write is under way, as #1258 had it. - pager_create, the SDK's Pager::create and pagerprobe take no object count. - Busy came from both sides. #1261 on main made it general, an inbox's submitter among its uses, mapped to EBUSY and ResourceBusy; #1258 used it for a write to a file a program runs from, mapped to ETXTBSY and ExecutableFileBusy. It keeps main's mapping, and its doc names both uses: a holder's write to a sealed object now answers EBUSY. - The file server's tests take #1273's keeps argument (None where the count is not what a test is about) and #1258's record. - A slot grows from #1273's 72 bytes to 216: #1258's object state, record and writers, and the second mark. The /tmp record's per-file cost says so, measured as #1273 measured it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he last holder is refused The seal refused a holder's write as `Busy`, which main's #1261 had made an inbox submitter's word, so one kind said two things and libc mapped them to one errno. The seal now answers `TextBusy` (12): libc gives ETXTBSY and std `ExecutableFileBusy`, and `Busy` keeps its one meaning. A holder's write that begins once the object's last holder went is refused as `Gone`: the pager may already have been told no holder is left, and the write would dirty what it was told no one holds. The no-holders notice is re-checked against writes under way as well as holders when it is taken. `owe_dirtied` goes; its two callers mark and unmark the dirtied list directly. The slot table's test marks slot 3 on both lists, so a removal that unmarks only one list fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…leave the kernel The kernel kept, for every process, the labels its parent put on each endowment, compared three of them (`job`, `self`, `quit`), answered them back through `SYS_ENDOWMENTS`, and held a namespace object that mapped service names to connectors behind three more calls. None of that is the kernel's: it is what one program calls a handle it gave another. Now a spawn moves a list of handles and nothing else. The child's table starts with its handle to itself (`RawHandle::SELF`), its notice (`RawHandle::NOTICE`), then the endowments in the order the spawn listed them (`RawHandle::endowed(i)`); the kernel installs them so and asserts it. What the parent calls each is a table it writes in the child's startup block, which the child's SDK checks once, whole, before it looks one up: a table that is no list of distinct, non-empty UTF-8 names ends the child, said on its `fd:2` where the table still names one. - `SpawnArgs::job` replaces the `job` label: a borrowed handle carrying `WRITE`, resolved once like the place and the pager, so the commit's re-check that the moved handle still named the job goes. - `SYS_CONNECT` (102, `SYS_NAMESPACE_OPEN`'s number) connects through a connector carrying `WRITE`, which a connector is now minted with. One without it may only be passed on. - `SYS_ENDOWMENTS` (99), `SYS_NAMESPACE_BUILD` (101), `SYS_NAMESPACE_NAMES` (170), the namespace object, `SpawnEndow`, `EndowEntry`, the label blob and its per-process charge go; the freed numbers stay unassigned. `MAX_SPAWN_ENDOWMENTS` is 96: the 30 a spawn carried plus the 64 a namespace held, so everything one table and one namespace expressed still fits. The names are bounded by the startup block alone. - `ObjectKind::Namespace` goes and its word 8 stays unused, as `ObjectKind` says a word that goes does: `SysCap` through `DeviceSet` keep 9 to 19, `from_word` looks a word up rather than indexing by it, the SDK's census finds a kind's count by its place in `ALL`, and the SDK's receive test takes word 8 and the word past the last kind as ones no kind has. - The supervisor, whose parent is the kernel, starts holding its system capability, the file server's image, ROOT's and the boot names at the four handles `toyos_abi::boot::supervisor` states. In userland a service is a connector named `svc:<name>` (a row's `receives`, its view's `fs:` directories, minted grants) or `provide:<name>` (one a launching client sent). `endow::service` looks one up and connects; a helper is given a duplicate of each its parent holds, narrowed by `CommandExt::services_only`. Everything else is taken once by name. The supervisor gives a provided connector to a row that does not receive it without `WRITE`; swap's connector arrives with `WRITE` alone, as its one holder only connects. The supervisor adopts its own directory capabilities (`std::os::toyos::fs::adopt_services`), which no child inherits: so a storage row, whose view is empty, now starts holding no file grant, where it inherited the supervisor's whole tree. A connector a peer sent, as a session's launcher and controls are, is typed only where the kernel reported a connector (`ReceivedHandle::typed`), and an endowed one on its parent's word. std's `adopt_services` is `unsafe`, as `adopt_namespace` was: the handles it takes are its caller's word. `SYS_NAMESPACE_OPEN`'s name was the last caller of `SyscallContext::user_str`, since #1238 reads a spawn's path as bytes bounded by `MAX_PATH`. So `user_str` and `MAX_USER_STR` go. `KernelStack::heap` charged every thread two 64 KiB strings and a page for what a call copies in. It now charges, by their types, what a spawn copies in and holds where it may be preempted: - its path, name and build-id at their bounds; - its ranges; - its endowments; - the mappings of the device claims it moves, in a vector sized once at the first claim so it never grows past that. It claims nothing for another call. A block session holds a connector and no service name, so diskserver's two messages for a session's server that broke the protocol or went silent name the session by its partition GUID. #1261's `poll_belongs_to_its_handle` connects through its connector, where it opened a one-entry namespace. Kernel: 56,188 lines in 212 files at main d1cefba to 55,830 in 211. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `testcases` boot owed on metal for #1252 now names the four shared-boot members whose rebased forms have only been compiled: #1261's `poll_belongs_to_its_handle`, which connects through its connector where it opened a one-entry namespace, and `messages_are_whole`, `moved_rights` and `mutual_kill`. No guest row runs them until #1271's shared-boot row lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
A ring kept its polls by handle, but an object's watch kept its entries by nothing. A close could therefore end every ring's poll on the object or none, and
close_ends_pollschose which wrong answer each object kind got:Process,SysCap,Jobor claim watch was made through left that watch armed until its object posted;deregister, andtoyos::PollerwasSyncover a two-step tail move.Resulting behaviour (the design approved in the design poll; decisions settled 2026-11-11)
NotFound. Closing another handle to the same object answers nothing. A refused move ends nothing.close_ends_polls, its per-kind table, the three list locks every close took, the acceptor's cancel at its last handle, and thekernel/CLAUDE.mdcaveat that pointed at it are all deleted.cancel_ringsstays for a source that ends under live handles, such as a claim's release.inbox_submitclaims the ring with one exchange and holdsSubmittinguntil it returns. A second concurrent submit is refused with a new ABI error,Busy(SyscallError11; stdResourceBusy, libcEBUSY). The completions are reachable only throughSubmitting, so the room an answer was read for is the room it is written into. An answer no longer posts the ring's watch, andKept::lookingis gone. Closing a ring wakes its submitter, which returnsNotFound.toyos::PollerisSendand no longerSync.ToyOSOrg/miotoyosat4108eff, a fast-forward of the pinnedfe42def): a registration carries a generation. The drain drops a completion whose registration is gone or was made again, under the lockderegistertakes. No cancel op is added to the ABI.kernel/pure/sched/poll.rs.kernel-loomalready compiles that path, and its by-path copies ofinbox/once.rsandinbox/polls.rsare gone.ring_park_herdis now 256 threads, each parked insubmiton a ring of its own and woken by an event each round. It remainsmask_windows' load (decision 1).architecture/kernel.md's "One way to wait" states both rules.Records
a-close-of-one-handle-ends-every-rings-poll-on-its-objectand its duplicateclosing-a-pipe-end-ends-every-holders-polls-on-it, closed together;a-watch-outlives-the-close-of-the-process-handle-it-was-made-through;mio-deregister-fd-leaves-a-pending-poll-live;closing-a-ring-leaves-its-submitters-parked;two-submitters-of-one-ring-race-its-last-completion-slot;toyos-poller-is-sync-and-a-watch-moves-the-tail-in-two-steps.mios-selector-arms-no-registration-made-while-another-thread-selects(decision 3; found by reading, not fixed here).a-process-lengthens-…-on-one-ringbecomesa-claims-holder-lengthens-its-handlers-interrupts-off-walk-by-the-polls-it-arms. Its first bullet, N threads on one ring, can no longer happen. The small-kernel track's stage 6 step 2 says what the herd now is.an-irq-watchs-freeing-cancel-compiles-in-a-handler(end_pollsfrees ascancel_pollsdoes),a-second-poller-on-a-windows-handle-stalled-the-prompt(its two related records are fixed), andnothing-fails-when-a-devices-release-or-close-stops-answering-its-polls. That last record names the new close path,WatchRef::end_polls'sClaimarm, and its exit is restated for it (review round 2).a-second-poller-on-a-windows-handle-stalled-the-prompt(cause unknown) andthe-two-watch-poll-model-counts-a-dropped-watchs-answer-as-its-completion.Checks
The owner's testing policy applies: the touched crates' tests and clippy shapes, the loom models and controls this change touches, and the image. Runs 3 and 4 were at
f79df9574plus this change's tree. Run 4 reran everything that edits after run 3 could change: one model rewritten, redundant clones removed, and the pure tests' ring counter. Those edits cannot change run 3's other results.cargo test -p kernel --lib --features sched-check(run 4)sched::poll's new tests andwatch_list'send_ringstest; 462 before the editscargo test -p kernel-loom --tests --no-run(run 3), then--test loom_watch --test inbox_answer --test poll_once(run 4)build/ci.rsruns them:poll-fire-load-store(poll_once; loom_watch's four, the new close model among them, run 4),post-is-an-answer,commit-ignores-notify,register-outside-table,admit-keeps-the-replaced,teardown-posts-nothing(run 3)cargo test -p kernel-sim --no-runRinggainsviacargo test -p toyos-build --lib(run 3)llvm::tests::the_caller_s_environment_reaches_neither_the_build_nor_the_key, which isissues/the-llvm-environment-test-breaks-git-on-macos.md. The same red on PR #1239's tree, with none of this change's build edits (run 4). 383 passedcargo test --test toyos-checks(run 4)cargo test -p toyos --lib pollerboot-actuators,test-actuators,kernel-loom/kernel-sim/toyos-build--all-targets(run 4),toyos --all-targets,toyos-abicargo run -- --build-only(run 3)Busy, libc, every image programf897ae424(round 1)4108eff: the build's dep-info namescheckouts/mio-…/4108eff/src/sys/toyos/selector.rsI did not run aarch64 locally: the change has no arch code. The shared watch beneath both architectures goes to the guest runner's
virt_jobs_at_el2.Guest. This change's guest claims are
poll_belongs_to_its_handleand the reshapedring_park_herd. Both are shared-boot members on metal, not QEMU rows, so no guest row runs them. With the benches down they are owed on metal (below). QEMU rows that exercise polls through the SDK, libc, netstack and the compositor, and one AArch64 row for the shared watch, are dispatched as one batch. Run 43064052517 atdd1d57622, 5 passed of 5 (session_lifecycle,stream_holders,libc_children,virt_jobs_at_el2,netstack_holders_exported_to_children; log). The later rounds change nothing these rows build.Evidence (
ToyOSOrg/evidencepr-1261atf29103ba): index, run 3 (summary), run 4 (summary), metal staging and request.At the current head
39f9da686, rebased onto #1246: guest run 43108631616 covers it at that head. It runs the same five rows,virt_jobs_at_el2among them for the shared watch beneath both architectures. Round 1's one code change, the guest tests' mio pin, reaches onlymio_waker_coalesces, a metal member. The result follows when it arrives.Owed on metal (the benches are down; metal is no longer a landing condition). Each item is recorded in a record this PR updates:
issues/nothing-fails-when-a-devices-release-or-close-stops-answering-its-polls.md):testcases,testcases-watchdogandshared-debugboots of main, whole, on the T14.poll_belongs_to_its_handle's four progress lines and: ok,pipe_other_end_gonepassing, and every other shared member that polls passing.issues/mios-selector-arms-no-registration-made-while-another-thread-selects.md):mio_waker_coalescesin main'stestcasesboot on the T14, passing, built from mio4108eff.issues/a-claims-holder-lengthens-its-handlers-interrupts-off-walk-by-the-polls-it-arms.md):mask_windows, thewindowscaseboot of main on the T14. Main must show it passing andtest_rs_ring_park_herdpassed, which also shows the job admits the 256 rings.benchcaseboot of main from before this change against main from after it, two boots a side, in the order B, H, H, B. A row is slower only if all its head p50s lie above all its base p50s over four boots a side. The first two a side are a screen, and a flagged row gets a second round. Each row is reported as its after/before ratio beside both sides' ranges.The images staged for the queued boots are deleted. The request (r2) stays as the record of what was asked.
Review round 1 (
f897ae424)tests/guest/rust-testskeeps its own lockfile, which still pinned mio atfe42def. So my earlier claim that staging compiled4108effwas false: the first staging built the old selector, and the fix shipped in no ToyOS binary. That lockfile now pins4108eff. The re-staged build's dep-info names4108eff's selector, andmio_waker_coalesceswas relinked after it (dep-info). Every lockfile that names the fork now pins4108eff.system/netstack/src/watches.rsandsdk/toyos/tests/transport_loom.rsnow citekernel/pure/sched/poll.rs.issues/parallel-tests-red-under-other-suites.mdno longer names the deleted gate: what it held is structural now.Review round 2 (
8e8b85256)45935f4b9. Main deletedissues/parallel-tests-red-under-other-suites.md, and the deletion is kept, so round 1's edit to it is gone, along with that sentence of its commit message.git range-diffshows the main commit unchanged. No code changed, so the images staged atf897ae424stay valid.issues/nothing-fails-when-a-devices-release-or-close-stops-answering-its-polls.mdnow names the close path this change made:ops::end_polls,WatchRef::end_polls'sClaimarm, thenDeviceClaim::end_polls, thenpcidev/isa::end_polls. It also names the release that still answers live polls,tear_down_departed. Its exit asks for a test of each path, red with that call removed. The audio claim it named no longer exists.r2/lock-check-rust-tests.log). It resolves mio at4108eff, exit 0.Rebase onto #1246 (
06fcb81ea)kernel/src/inbox/mod.rs,resolveand the newThisProcess. A thread carries its process's handles, and a lookup takes no lock but the table's #1246 maderesolvelook up throughprocess::with_handles. This PR maderesolveanswer the table's id beside the object, and addedThisProcess::while_held. Both now usewith_handles, so a poll's registration, a close'sremoveand a send'stransferall hold the handle table's own lock. P2's argument is unchanged: each happens in one hold of the table that owns the handle.kernel/pure/sched/watch.rs's imports. The machine's stop keeps its gate and waits for no thread #1241, which merged before A thread carries its process's handles, and a lookup takes no lock but the table's #1246, removed the watch'sGateand the atomics it used. The import is main's pluspoll::Via.HandleTable, the endowment'sremoveunder A thread carries its process's handles, and a lookup takes no lock but the table's #1246's lock inloader/start.rs, and The machine's stop keeps its gate and waits for no thread #1241's removal of the gate model and control beside this PR's loom models and control rows.loom_watchno longer usedCPU1, and clippy refused the import;39f9da686removes it.fe1128e76, the kernel's host tests and clippy shapes (host library, x86-64 default and with actuators, AArch64), the loom modelsloom_watch,inbox_answerandpoll_once, andevery_model_control_is_run, each exit 0. kernel-loom's clippy exit 0 at39f9da686(summary). Guest: run 43108631616 at39f9da686, with the same five rows. Run 43092106101 was cancelled as superseded.Risks
Onceas the control for "a replacing watch racing a post". It cannot red a one-answer claim, becausePollskeeps one entry per handle. That model's control isadmit-keeps-the-replacedinstead. The splitOncereds "a close racing a post", which counts the takes.Pollunder debug assertions, and netstack's TCP types. A guest test that deregisters a TCP source under a fired watch would show it; none is written here.Busyagainst One table reads every refusal, each runtime keeps one from it to its word, and _SC_ARG_MAX is what a caller may pass #1227: One table reads every refusal, each runtime keeps one from it to its word, and _SC_ARG_MAX is what a caller may pass #1227'sFrom<SyscallError> for Failurehas no wildcard, so whichever lands second addsSyscallError::Busy => Failure::Busy. One table reads every refusal, each runtime keeps one from it to its word, and _SC_ARG_MAX is what a caller may pass #1227 already mapsFailure::BusytoEBUSYandResourceBusy.🤖 Generated with Claude Code