Repository navigation
Conversation
A user copy reaches its pages under the current space's lock, populating the ones not yet touched. A caller that copied while holding that lock spun on itself until the ticket lock's 500M-spin DEADLOCK panic, and only when a page in the window happened to be unpopulated: a hazard that depended on coverage. The space now records which CPU holds its lock (SpaceGuard sets it, its drop clears it; holding a sync::Lock keeps the holder on its CPU). Every copy entry, View::window, View::pieces and translate_user, refuses a copy this CPU makes under that lock with a named panic, whether or not any page needs populating. The check is one relaxed load and compare per copy entry. The finding's other locks, PROCESS_TABLE, process_data and the VFS lock, are no longer taken by a copy's fault-in: handle_page_fault and AddressSpace::reach take the space's lock alone. So that order is refused by the check and no argument about callers is needed. The swap track's S0-a row keeps its larger half: every user copy outside every kernel lock (the pipe and message copies), which memory stage 7b needs before a fault may park on a pager. Records: the orchestrator's ruling of 2026-11-01 (on the owner's delegation) that memory stage 7b comes before stage 4, in the process-memory track. Closes issues/a-user-copy-demand-pages-under-whatever-its-caller-holds.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu
added a commit
that referenced
this pull request
Nov 2, 2026
…fused (S0-a) A user copy reaches its pages under its space's lock, populating any not yet touched, and in memory stage 7b that fault-in will park on a userland pager. A copy made under any spin lock would then park holding it. Several syscalls copied that way: reads and writes under the process lock and the object's (a pipe's, a kernel file's cache, a console's line, a device claim's description), message send and receive under the process and queue locks, and getcwd, query_modules, sysinfo and readlink under the process, process-table or VFS lock. The gate. Every copy entry (View::window, View::pieces, translate_user) refuses a copy above the preemption depth its syscall's entry runs at, whether or not a page needs populating: every spin lock raises the count, and both architectures' trap entries raise it to BASELINE_TRAP (x86_64's syscall_entry and common_entry, AArch64's trap::syscall and trap::user_abort). It is a kernel-bug fence; no user action reaches it. This replaces #1146's per-space holder, which caught only a copy under its own space's lock. The moves. - Syscalls work on their object cloned out of the process's lock (syscall::handles::object, and with_object now clones). A device claim is read under it, since describing one installs handles, into a DeviceRead copied out after. - getcwd clones the cwd, readlink lets the VFS lock go first, and query_modules and sysinfo lay their answer out under their lock and copy it out after. - A kernel file's position and a console's line are sleeping locks (SleepLock), which raise nothing, so a read or a write stays atomic with its position update (XSH 2.9.7) and a console write keeps its lines whole. file_cache reads copy from the page's Arc with the cache's lock let go (a write meanwhile writes a copy), and a write's bytes are copied in a page at a time before the cache's lock. ByteSource had no other user and goes. - Pipes and connections claim their bytes (kernel::pipe::Bytes): one write claim and one read claim at a time, each copying with no lock held. The lock claims, hands out a chunk's frames and ends the claim. A claimed write's frames count as used, so no surplus takes them, and only the read's own consume lets claimed bytes go; a last reader leaving under a claim leaves the frames to the claim's end. A second copy of either kind waits for the first on its end's watch under a TURN token, cancellable by a kill and waking no poll. Frames and a longer frame list are taken before the lock and spares given back after it (frame_bytes::supply, give_back), so neither the allocator nor the heap is reached under it. - A receive claims the front message, copies its bytes out, then under the process and queue locks pops it and installs its handles, with the table's room made first. A claim dropped first leaves the message queued whole, so a refusal still takes nothing. A send claims its room, copies in, then moves its handles and queues the message whole in one hold; a receiver gone by then hands the batch back at its own numbers. The console's partial line is now flushed in Drop, at the last reference: the deferred release queue runs on_zero_handles where no sleeping lock is taken, and a write in flight holds a reference. Records: the finding a-user-copy-demand-pages-under-whatever-its-caller-holds is closed (its order is refused by a check that fails). The pipe issue keeps what is left: the hold time unread on a bench, the queue's two lists grown under its lock, and audio_tone's worst wake. The process-memory track records the orchestrator's ruling of 2026-11-01 (on the owner's delegation) that 7b comes before stage 4, its S0-a prerequisite met, and that a copy under a sleeping lock parks holding it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu
added a commit
that referenced
this pull request
Nov 2, 2026
…fused (S0-a) A user copy reaches its pages under its space's lock, populating any not yet touched, and in memory stage 7b that fault-in will park on a userland pager. A copy made under any spin lock would then park holding it. Several syscalls copied that way: reads and writes under the process lock and the object's (a pipe's, a kernel file's cache, a console's line, a device claim's description), message send and receive under the process and queue locks, and getcwd, query_modules, sysinfo and readlink under the process, process-table or VFS lock. The gate. Every copy entry (View::window, View::pieces, translate_user) refuses a copy above the preemption depth its syscall's entry runs at, whether or not a page needs populating: every spin lock raises the count, and both architectures' trap entries raise it to BASELINE_TRAP (x86_64's syscall_entry and common_entry, AArch64's trap::syscall and trap::user_abort). It is a kernel-bug fence; no user action reaches it. This replaces #1146's per-space holder, which caught only a copy under its own space's lock. The moves. - Syscalls work on their object cloned out of the process's lock (syscall::handles::object, and with_object now clones). A device claim is read under it, since describing one installs handles, into a DeviceRead copied out after. - getcwd clones the cwd, readlink lets the VFS lock go first, and query_modules and sysinfo lay their answer out under their lock and copy it out after. - A kernel file's position and a console's line are sleeping locks (SleepLock), which raise nothing, so a read or a write stays atomic with its position update (XSH 2.9.7) and a console write keeps its lines whole. file_cache reads copy from the page's Arc with the cache's lock let go (a write meanwhile writes a copy), and a write's bytes are copied in a page at a time before the cache's lock. ByteSource had no other user and goes. - Pipes and connections claim their bytes (kernel::pipe::Bytes): one write claim and one read claim at a time, each copying with no lock held. The lock claims, hands out a chunk's frames and ends the claim. A claimed write's frames count as used, so no surplus takes them, and only the read's own consume lets claimed bytes go; a last reader leaving under a claim leaves the frames to the claim's end. A second copy of either kind waits for the first on its end's watch under a TURN token, cancellable by a kill and waking no poll. Frames and a longer frame list are taken before the lock and spares given back after it (frame_bytes::supply, give_back), so neither the allocator nor the heap is reached under it. - A receive claims the front message, copies its bytes out, then under the process and queue locks pops it and installs its handles, with the table's room made first. A claim dropped first leaves the message queued whole, so a refusal still takes nothing. A send claims its room, copies in, then moves its handles and queues the message whole in one hold; a receiver gone by then hands the batch back at its own numbers. The console's partial line is now flushed in Drop, at the last reference: the deferred release queue runs on_zero_handles where no sleeping lock is taken, and a write in flight holds a reference. Records: the finding a-user-copy-demand-pages-under-whatever-its-caller-holds is closed (its order is refused by a check that fails). The pipe issue keeps what is left: the hold time unread on a bench, the queue's two lists grown under its lock, and audio_tone's worst wake. The process-memory track records the orchestrator's ruling of 2026-11-01 (on the owner's delegation) that 7b comes before stage 4, its S0-a prerequisite met, and that a copy under a sleeping lock parks holding it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
Japabu
added a commit
that referenced
this pull request
Nov 3, 2026
…fused (S0-a) A user copy reaches its pages under its space's lock, populating any not yet touched, and in memory stage 7b that fault-in will park on a userland pager. A copy made under any spin lock would then park holding it. Several syscalls copied that way: reads and writes under the process lock and the object's (a pipe's, a kernel file's cache, a console's line, a device claim's description), message send and receive under the process and queue locks, and getcwd, query_modules, sysinfo and readlink under the process, process-table or VFS lock. The gate. Every copy entry (View::window, View::pieces, translate_user) refuses a copy above the preemption depth its syscall's entry runs at, whether or not a page needs populating: every spin lock raises the count, and both architectures' trap entries raise it to BASELINE_TRAP (x86_64's syscall_entry and common_entry, AArch64's trap::syscall and trap::user_abort). It is a kernel-bug fence; no user action reaches it. This replaces #1146's per-space holder, which caught only a copy under its own space's lock. The moves. - Syscalls work on their object cloned out of the process's lock (syscall::handles::object, and with_object now clones). A device claim is read under it, since describing one installs handles, into a DeviceRead copied out after. - getcwd clones the cwd, readlink lets the VFS lock go first, and query_modules and sysinfo lay their answer out under their lock and copy it out after. - A kernel file's position and a console's line are sleeping locks (SleepLock), which raise nothing, so a read or a write stays atomic with its position update (XSH 2.9.7) and a console write keeps its lines whole. file_cache reads copy from the page's Arc with the cache's lock let go (a write meanwhile writes a copy), and a write's bytes are copied in a page at a time before the cache's lock. ByteSource had no other user and goes. - Pipes and connections claim their bytes (kernel::pipe::Bytes): one write claim and one read claim at a time, each copying with no lock held. The lock claims, hands out a chunk's frames and ends the claim. A claimed write's frames count as used, so no surplus takes them, and only the read's own consume lets claimed bytes go; a last reader leaving under a claim leaves the frames to the claim's end. A second copy of either kind waits for the first on its end's watch under a TURN token, cancellable by a kill and waking no poll. Frames and a longer frame list are taken before the lock and spares given back after it (frame_bytes::supply, give_back), so neither the allocator nor the heap is reached under it. - A receive claims the front message, copies its bytes out, then under the process and queue locks pops it and installs its handles, with the table's room made first. A claim dropped first leaves the message queued whole, so a refusal still takes nothing. A send claims its room, copies in, then moves its handles and queues the message whole in one hold; a receiver gone by then hands the batch back at its own numbers. The console's partial line is now flushed in Drop, at the last reference: the deferred release queue runs on_zero_handles where no sleeping lock is taken, and a write in flight holds a reference. Records: the finding a-user-copy-demand-pages-under-whatever-its-caller-holds is closed (its order is refused by a check that fails). The pipe issue keeps what is left: the hold time unread on a bench, the queue's two lists grown under its lock, and audio_tone's worst wake. The process-memory track records the orchestrator's ruling of 2026-11-01 (on the owner's delegation) that 7b comes before stage 4, its S0-a prerequisite met, and that a copy under a sleeping lock parks holding it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Memory track S0-a, the finding
issues/a-user-copy-demand-pages-under-whatever-its-caller-holds.md: its exit, by a check that fails rather than an argument about callers.Problem
A user copy reaches its pages under the current space's lock, populating any it hasn't touched yet. A caller that copied while holding that lock spun on itself until the ticket lock's
DEADLOCKpanic at 500M spins. And it happened only when some page of the window was still unpopulated, so the bug hid wherever the test coverage happened to populate the pages first.Now
SpaceGuardsets it on acquire and its drop clears it. Holding async::Lockdisables preemption, so the holder stays on its CPU.View::window,View::piecesandtranslate_user. The cost is one relaxed load and compare per copy entry.PROCESS_TABLE,process_dataor the VFS lock:handle_page_faultandAddressSpace::reachtake the space's lock alone.Not in this PR
The swap track's S0-a row has a larger half: every user copy outside every kernel lock. The pipe and message copies still run under their locks (
issues/a-pipes-lock-spans-a-user-copy-and-its-first-allocation.md). Memory stage 7b needs that half before a fault may park on a pager, and it is the next step.Records
The orchestrator's ruling (2026-11-01, on the owner's delegation) that 7b comes before stage 4, in the process-memory track.
Checks
Evidence: https://github.com/ToyOSOrg/evidence/tree/da30fd3c533732da42cd43e9160fd95445cb1d8c/r0
cargo run -- --ci host: rc=0, 108 steps green.--debug: 5 members exit 0, and the boot raised no refusal.sys_paged_readholds the space's lock across its copy. It is refused at once, by name, with a backtrace throughPagerObject::read.kernel/srcand has no pure model. The guest boot is its coverage.🤖 Generated with Claude Code