Repository navigation
Conversation
`osbench_disk` runs `files`, `nvme` and `persist` alone on the bench's NVMe, so a change to the disk's path is measured without the whole suite's boot. It is the row PR #1131 introduced; it lands here first because the forced-write change that follows is measured with it, and its base arm needs the row too. Only a pass that names it runs it: the nightly `osbench` boot measures the same groups. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Nothing in the native write path could make one write durable by itself: the block protocol, the NVMe driver, the USB mass-storage driver and bcachefs knew only a write and a flush of the whole device. So every bcachefs journal entry was written between two flushes, and an entry naming itself between three. A write may now be asked with FUA, and is on the medium when it is answered: - `BlockDev::write_durable`. Its default is the fallback, stated once: a device that cannot force one write writes it and then flushes. - The block protocol's write carries `fua` (`toyos_blockring::entry`), and an open's answer says whether the session's device takes it. A server refuses a forced write it did not offer. A forced write acknowledged is recorded under no loss count and kept by no client: no reset can take it, so nothing is issued again for it. The caller's side of that is a contract the crash model checks: a forced write never goes over a write of the same client no flush has covered. - NVMe sets FUA in the Write command (NVM Command Set 1.0 §3.2.6, Command Dword 12 bit 30). Every controller takes the bit, so every NVMe session offers it. - USB mass storage sets FUA in WRITE(10) (SBC-3 §5.32, byte 1 bit 3) where the device's mode parameters set DPOFUA, which the driver now asks with one MODE SENSE(6) as a disk first comes up. A disk that does not say so is offered without, its line says so, and its clients write and flush. A forced write leaves no flush owed when the device leaves. - The file server's cache forces a durable write's blocks onto the disk alone, in the flush's place in line, with no flush of the disk. - The journal writes each entry after one flush and forced. An entry naming itself is forced headless and then its header block forced: no flush between. Since the cache asks the disk for no flush when nothing was written since the last, a commit of names alone is one forced write. Tests, all on the host. The protocol's exhaustive model gains forced writes and the law that one answered done stays on the medium through every reset, death and device error; its control sends the write unforced and reds that law. The crash model takes a forced write as torn until it completes and durable after, and refuses one over an unflushed write. The journal's crash tests judge every state while an entry is still torn and once it is forced; a new test holds a commit to exactly one flush and one forced write, and shows a device that leaves the entry in its cache losing a committed create. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The protocol's header still said a completion never means durable. A write asked with `fua` is the exception the forced-write change made. A comment alone. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Review r1 of #1202 at
|
| Item | Code | Verdict |
|---|---|---|
| NVMe Write FUA | Command Dword 12 bit 30 (1 << 30), count zero-based in bits 15:0 |
correct; no capability bit gates it, so offering it always is right |
| WRITE(10) FUA | byte 1 bit 3 (0x08) |
correct |
| MODE SENSE(6) CDB | 1A 00 3F 00 C0 00: current values, all pages, 192 allocated |
correct |
| DPOFUA | bit 4 of byte 2 of the 6-byte mode parameter header | correct |
The two citations by clause title are honest and findable, which is better than an unchecked number. The numbers should be filled in by someone holding SPC-4 and SBC-3. SBC-3 §5.32 reuses the file's existing citation, which I could not check either.
Protocol and fallback
- Validation:
Request::decodetakes the flag only on a write and refuses any other bit and any flag on a read or flush.takerefuses an unoffered forced write unissued, after the read-only check.Opened::decoderefuses a flag byte that is neither 0 nor 1. - Wire compatibility:
Openedgrows to 25 bytes with an exact-length decode, and a request's sixth word goes from reserved to flags. Every consumer is in this workspace and lands together; nothing underrust/libraryuses it. - Fallback: explicit and logged by both drivers. BlockDev wrappers that do not forward
write_durable(the installer's) take write-then-flush, which is correct.
osbench_disk
Fine to carry. It is named_only, so no unfiltered or nightly pass takes it, and the word osbench does not select it. It reuses benchcase.toml, whose three jobs exist, and the suite's judge and bound, with the bound's reuse stated. Its first run is the staged A/B.
Evidence
- Host:
host/ci-host.logat8a8ff27b9shows every new testokand the single llvm red. I accept that red as independent, since it is recorded and no file here is near it. - Guest: the log shows
8a8ff27b9checked out and three PASS. Onlynvme_disk_bootsreaches a forced write, on QEMU's NVMe. The stick rows show MODE SENSE did not break QEMU's stick and nothing more, as the PR says. - Head:
b8e7fe921changes one module comment, so the results carry. The "doc tests ran again at the head" claim has no log, and needs none.
What the USB path still needs
No real stick has seen either new command. In order of cost:
- Which line QEMU's stick produced: the print suggested above, then one dispatch of
the_log_lands_on_the_boot_stick. - MODE SENSE on real sticks: one bench boot of any image with a stick plugged into a port that is not the boot disk, repeated for each stick at hand. The readback must show
serving it as disk N: ...with either ending, and noa Bulk-Only round trip brokeorthe ladder climbsbefore it. This needs no new row and can ride the staged A/B boots. A stick that hangs on this command is never served, because the command runs before the disk arrives on every bring-up. I would not land before this. - WRITE(10) with FUA accepted: this needs a device that sets DPOFUA (bridges usually do, plain sticks rarely) carrying a bcachefs volume. No row serves a non-boot stick's volume, so it stays owed under blocker 3's exit.
- FUA actually honoured: only a power cut after an acknowledged commit shows it. That is evidence to record if ever taken, not a gate.
1. The rule that a forced write never goes over a kept write was prose and a panic in the crash model. A violation is silent: after a loss the kept bytes are issued again over the forced ones and a later flush calls them durable. The block client, which alone holds the kept set, now panics when a write with `fua` is next and a kept write overlaps it. The journal's comment gave an incomplete reason for holding the rule: a refused durable write can leave its blocks written and unflushed, and it is each attempt's own flush that covers them. A device double that refuses the first forced write holds that flush inside the retry: Flush, Durable, Flush, Durable. 2. A reconnect compared the whole open's answer, `fua` included, so a device that answered MODE SENSE differently after a driver restart lost its volume. A reconnect is held to the partition's length and GUID and adopts the new answer for forced writes. Both comments that claimed as much are now true, and the refusal of a forced write the new server does not offer is reached by a test. 3. The USB record's forced-write gap has its own section and its own exit, and says what a stick that sets DPOFUA and then refuses or ignores FUA does. Suggestions taken: a mode header whose MODE DATA LENGTH ends inside it is not believed; the MODE SENSE comment says why all pages and 192 bytes; `the_log_lands_on_the_boot_stick` prints the stick's bring-up line; `write_durable` promises nothing else durable. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
Closing by the orchestrator's decision: measured and declined. The journal keeps write-plus-Flush, and the forced-write plumbing, which had no other user, does not land. The branch is deleted; its last head was Result ( AMD, every boot of each arm agreeing, no overlap:
T14: the drive's slow mode decided every row. Slow against slow the arms were equal (5.66-6.24 ms against 5.78 and 5.92 ms); the forced arm had no fast boot. Forced writes in the fast mode, read off #1131's boots: No faster at best, 66% slower at worst. Not established: whether the AMD's 4.00 ms (one boot: min 3.71, p50 4.01, p90 4.25) is the drive's cost of a forced write or a cadence on ToyOS's side. No boot timed single commands. What survives is in #1215: the |
`osbench_disk` runs `files`, `nvme` and `persist` alone on the bench's NVMe, so a change to the disk's path or its power states is measured without the whole suite's boot. PR #1202's and PR #1131's A/B ran on it. Only a pass that names it runs it: the nightly `osbench` boot measures the same groups. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…low mode ending in idle PR #1202 wrote the bcachefs journal's entries with Force Unit Access, one Flush fewer per commit. Its A/B on both benches found it no faster at best (the T14, fast mode) and 66% slower at worst (the AMD), so it is closed and nothing of it lands. The record says what was measured, on which revisions, and the one open question, so it is not retried blind. The same boots showed the T14's slow durable writes ending during the after-idle row's 80 s of idle in four boots of four. That arm also set APST, so which of the two ends the mode is not separated; recorded as a finding with the measurement that separates them as its exit. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Problem
Nothing in the native write path could make one write durable by itself. The block protocol, the NVMe driver, the USB mass-storage driver and bcachefs knew a write and a flush of the whole device, and nothing else. So each bcachefs journal entry was written between two flushes (flush, entry, flush), and an entry that names itself between three.
Behavior
BlockDev::write_durablereturns once its blocks are on the medium, and makes nothing else durable.Served::write_shared_durable) writes and flushes when it does not; a server refuses a forced write it did not offer. Every other device (RAM, the crash model, wrappers that do not forward):write_durable's default. The driver's bring-up line says which:a forced write is a Write with FUA(NVMe),DPOFUA set, so a forced write is a WRITE(10) with FUAorno DPOFUA, so a forced write is a write and a flush(USB).Decisions worth a reviewer's eye
osbench_disk(first commit) is the metal row nvme: the controller's own idle power states, by APST #1131 introduced:files,nvmeandpersistalone. It lands here because this change is measured with it and the base arm needs it. Only a pass that names it runs it.Checks
The change was checked at
8a8ff27b9; the review's fixes at the head,fea712a16.cargo run -- --ci hostatfea712a16llvm::tests::the_caller_s_environment_reaches_neither_the_build_nor_the_key, red on every macOS host (issues/the-llvm-environment-test-breaks-git-on-macos.md).cargo run -- --build-onlyatfea712a168a8ff27b9:nvme_disk_boots,the_data_partition_beside_the_stick_is_designated,the_log_lands_on_the_boot_stickfea712a16serving it as disk 0: DPOFUA set, so a forced write is a WRITE(10) with FUA.mutate-fua-goes-out-unforcedredsmodel::tests::a_forced_write_answered_done_stays_on_the_mediuman_entry_written_again_after_a_refusal_follows_a_flush_of_its_ownredWhat the host tests establish:
Flush, Durable, and after dataWrite, Flush, Durable. The crash tests judge every state both while a forced entry is still torn and once it is on the medium.Evidence: https://github.com/ToyOSOrg/evidence/blob/5d1ed034f23eab59d4e5513ffa9cc26b5092288d/README.md, and for the review's fixes
r1/README.mdat the same commit.Metal: judged, and this PR is not ready
The A/B ran to its end: four boots of main with the row, this PR at
8a8ff27b9and #1131 on it, per bench, interleaved, under rules fixed before any boot. Evidence: https://github.com/ToyOSOrg/evidence/blob/7efbe1295aa54370b213719b4d4f460e7a103554/ab/README.mdpersist.write_4k_fsync2.36 ms (2.24..2.46) on main against 4.00 ms (4.00..4.01) here;append_4k_fsync+66%,create_fsync_unlink+71%,write_4k_fsync_beside_1m+55%,seq_write_fsync266 against 212 MB/s. On this drive a forced write costs about 1.6 ms more than the write and Flush it replaced. Whether the 4.00 ms is the drive's or a cadence on the host is not established; no boot timed single commands.So this PR should not land as it is. The options and a recommendation are with the orchestrator.
Risks
issues/no-bench-row-drives-usb-storage-since-the-benches-boot-off-nvme.mdrecords it. The guest row passed, but a green run keeps no serial log, so which of the two lines QEMU's stick produced is not known.issues/toyos-durable-writes-on-the-t14s-nvme-run-six-times-slower-in-some-boots.md), which can confound an A/B there. The rules compare mode by mode.🤖 Generated with Claude Code