Repository navigation
std: available_parallelism is the kernel's CPU count, and an ended thread's stack comes back - #987
Conversation
Review r1 of #987 at f866fe3I read the commit, the whole diff, ArchitectureBoth fixes are in the right place. std's ToyOS backend (
FindingsBLOCKER
BLOCKER BLOCKER (evidence; the orchestrator runs it): SUGGESTION Implement the detached half now, mirroring libc's reap. That means a SUGGESTION SUGGESTION The CPU half compares std with the same syscall that std itself calls. That is enough to catch a regression to a constant, but not a kernel miscount (capacity versus online). The harness already knows the guest has two vCPUs. Asserting the printed count against that number would make the oracle independent at no extra cost. SUGGESTION TestsQEMU is the cheapest place for the stack claim, because
The evidence README names the revision and states that the tree was restored after the controls. Only x86-64 under QEMU was run. That is acceptable here: the code involved is architecture-neutral std code and a generic kernel counter. PR description (for the orchestrator)
SEND BACK |
|
Metal, head
|
Review r2 of #987 at 3a5fc40 (rebased on eeffc8b)I read the commit's whole diff, r1 blockers
The optional suggestions were all taken. The harness now holds the printed count against The reap list
FindingsBLOCKER
SUGGESTION In TestsThe three controls are the right ones and red on the right assertions:
The fixed run prints SEND BACK. The |
…read's stack comes back `available_parallelism` answered a constant 1 on every machine, on the stale ground that no syscall said how many CPUs there are; `SYS_CPU_COUNT` does, and libc already answers `_SC_NPROCESSORS_ONLN` from it. Every Rust program on ToyOS now sees the machine's CPUs, so a pool sized from std starts more threads than before. No thread std started gave its 2 MiB stack back. `join` now frees it once the kernel has reaped the thread. A handle dropped without a join detaches the thread, as every `thread::scope` thread whose handle is not joined is: the thread then hands its block to a lock-free list as it ends, and the next spawn joins it and frees its stack, as libc's `pthread_detach` does. That join also collects the thread from its process's table, so detached threads no longer use up `MAX_THREADS`. Until a later spawn, an ended detached thread keeps both. `std_threads` holds all three: std's CPU count, which the QEMU check holds to the CPUs it booted the guest with; 64 joined threads not all on distinct stacks; and MAX_THREADS + 8 detached threads, each ended before the next spawn, all spawned and not all on distinct stacks. Stacks still allocated cannot share a base. It runs in the shared metal boot and under QEMU as `std_threads_count_the_cpus_and_give_back_ended_threads`. With the whole change, the join's free, or the detach reverted in turn, that run reds on its own assertion. Closes issues/std-says-this-machine-has-one-cpu.md, issues/std-leaks-a-thread-stack-per-spawn.md and issues/a-thread-std-detaches-holds-its-place-in-the-process-for-good.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- toyos-metal admits an image carrying one or two TOYOS-ROOTs beside its slot table, and refuses zero or three by name (Refusal::Roots), so the gate's doc says what it checks. - issues: an ended detached thread is reaped only at its process's next spawn, with the ff9b4b1 ps evidence and a scratch run at a head carrying #987 that shows the later spawns reaping the readers. - issues/root-in-memory-costs-its-whole-size.md: the measured cost per held child, 14.2 MiB at the base and 16.2 MiB with the spawn's image, which takes spawn_children_cap's 255 children past a 4 GiB guest; its exit is the memory design's stages 2, 4 and 6, as the orchestrator ruled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- toyos-metal admits an image carrying one or two TOYOS-ROOTs beside its slot table, and refuses zero or three by name (Refusal::Roots), so the gate's doc says what it checks. - issues: an ended detached thread is reaped only at its process's next spawn, with the ff9b4b1 ps evidence and a scratch run at a head carrying #987 that shows the later spawns reaping the readers. - issues/root-in-memory-costs-its-whole-size.md: the measured cost per held child, 14.2 MiB at the base and 16.2 MiB with the spawn's image, which takes spawn_children_cap's 255 children past a 4 GiB guest; its exit is the memory design's stages 2, 4 and 6, as the orchestrator ruled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- toyos-metal admits an image carrying one or two TOYOS-ROOTs beside its slot table, and refuses zero or three by name (Refusal::Roots), so the gate's doc says what it checks. - issues: an ended detached thread is reaped only at its process's next spawn, with the ff9b4b1 ps evidence and a scratch run at a head carrying #987 that shows the later spawns reaping the readers. - issues/root-in-memory-costs-its-whole-size.md: the measured cost per held child, 14.2 MiB at the base and 16.2 MiB with the spawn's image, which takes spawn_children_cap's 255 children past a 4 GiB guest; its exit is the memory design's stages 2, 4 and 6, as the orchestrator ruled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two defects in ToyOS's std, found by osbench (#983, which is stacked on this).
available_parallelismanswered 1It returned a constant 1 on every machine. The comment claimed no syscall says how many CPUs there are, but
SYS_CPU_COUNTdoes, and libc already answers_SC_NPROCESSORS_ONLNfrom it. It now returns the kernel's count of CPUs online. This closesissues/std-says-this-machine-has-one-cpu.md.Every Rust program on ToyOS now sees all of the machine's CPUs, so anything that sizes a pool from std starts more threads than before. Today no in-tree program does: tokio's only in-tree user,
system/sshserver, builds a current-thread runtime, and no lockfile carries rayon, crossbeam-deque,num_cpusorthreadpool. A future multi-threaded runtime or pool would start one worker per CPU, and each worker takes a 2 MiB stack. On the benches that is 8 on the T14 and 16 on the AMD bench, as std counted them in the osbench metal boots (bench-cpu-counts.txtin the evidence below, from an image that carries this PR'savailable_parallelism).No thread std started gave its stack back
joinnow frees the 2 MiB stack once the kernel has reaped the thread.thread::scopethread whose handle is not joined, becausescopewaits on its own counter, not on native joins. A detached thread now hands its block to a lock-free list as it ends, and the next spawn joins it and frees its stack. This is libc'spthread_detachpattern (sdk/libc/src/pthread.rs).MAX_THREADS.This closes
issues/std-leaks-a-thread-stack-per-spawn.mdandissues/a-thread-std-detaches-holds-its-place-in-the-process-for-good.md. It also correctsissues/a-processs-memory-is-a-byte-total-that-reads-zero-under-contention.md, which cited the first, and the comment inabuse_tls_alloc.rsthat blamed this leak.Checks
std_threads(tests/guest/rust-tests) holds three things:SYS_CPU_COUNT.MAX_THREADS + 8threads with dropped handles, each ended before the next spawn, are all spawned and are not all on distinct stacks.Stacks that are still allocated cannot share a base. Reuse also depends on dlmalloc returning the mapping and on the kernel placing the next one at the same address, so a future mmap randomisation would turn this red without any std defect.
3a5fc40cargo test --test toyos-build -- std_threads_count(QEMU q35, TCG, 2 vCPUs)2 CPUs;64 joined threads ran on 1 stacks; 4104 detached threads were all spawned and ran on 3 stackssdk/std/sys/thread.rsas on mainavailable_parallelism answers 1, and the kernel counts 2 CPUs64 threads spawned and joined one at a time had 64 distinct stacksdetached thread 923 of 4104 was refused: thread stack allocation failedcargo run -- --ci hostEach control was a patch applied to the fixed file and restored, and the file was compared byte-for-byte with the fixed copy afterwards. Logs and the control patches: https://github.com/ToyOSOrg/evidence/tree/a64c9f71b772a9b5388f7c70656a3999a78ed10a/std/r2.
🤖 Generated with Claude Code