code wiki / _hdl_build / nx_ws_cas.nx

nx_ws_cas.nx source

↩ module page · 55 lines · 3195 B

1// nx_ws_cas.nx -- WMS-R8: CONCURRENT-SSOT-SAFE registry write (advisory-lock serialized). 2// 3// module: nishi-core.wms.ws_cas 4// capability: CORE_COMPUTE (the registry's concurrent-writer durability floor) 5// 6// ROOT CAUSE this closes: ws_put_p (nx_workstream_store) is an UNGUARDED read-modify-write -- 7// ss_get (idempotency probe) -> ss_begin/ss_add -> ws_seg_next (= 1 + ss_manifest line count) 8// -> ss_commit (writes the seg files, then RMW-appends "seg-<id>" to manifest.txt via tmp+rename). 9// TWO concurrent ws_put_p calls race on BOTH the segid AND manifest.txt: each reads the SAME 10// manifest line count -> computes the SAME segid -> ss_write_seg clobbers the other's segment; and 11// each reads the SAME old manifest.txt, appends ITS one line, renames -> last-writer-wins, so all 12// but one concurrent manifest append is LOST. Net: committed records become UNRETRIEVABLE (the 13// classic lost update) even though each ss_commit's own tmp+rename is atomic. ss_commit gives 14// crash-atomicity, NOT inter-writer mutual exclusion -- the registry RMW needs a lock the substrate 15// never applied to it (the audit said the locking primitive was ABSENT; R0b added sys_flock). 16// 17// THE FIX (one capability): serialize the WHOLE ws_put_p RMW under an EXCLUSIVE advisory lock 18// (flock LOCK_EX) on a per-prefix lock file <prefix>wlock -- mirroring the WMS-R0b framed-append 19// lock frame. Two writers can no longer both be inside the manifest RMW window -> every commit gets 20// a distinct increasing segid and a non-clobbered manifest line -> ZERO lost updates. 21// 22// REUSE (rule 15): the lock frame is the proven nx_framed_append pattern (sys_flock LOCK_EX/UN from 23// nx_syscalls); the write itself REUSES ws_put_p UNCHANGED (additive -- the unlocked path stays for 24// genuine single-writer callers). Sovereign: nx_workstream_store + nx_syscalls only. 25// license_tier: ORIGINAL 26import "nx_workstream_store.nx" 27import "nx_syscalls.nx" 28 29// derive the per-prefix exclusive write-lock path: <prefix> + "wlock" (NUL-term) into out. 30// (a sibling of the store's manifest.txt; ONE lock guards the whole prefix's RMW.) 31func wc_lockpath(prefix: *u8, out: *u8) -> i64 { 32 var i: i64 = 0 33 while prefix[i] != (0 as u8) { out[i] = prefix[i]; i = i + 1 } 34 let suf: *u8 = "wlock" as *u8 35 var j: i64 = 0 36 while suf[j] != (0 as u8) { out[i] = suf[j]; i = i + 1; j = j + 1 } 37 out[i] = 0 as u8 38 return i 39} 40 41// ws_put_locked: ws_put_p serialized under flock(LOCK_EX) on <prefix>wlock so concurrent writers 42// can never collide on segid/manifest. Returns ws_put_p's verdict (0 committed / 1 unchanged / 43// <0 commit error), or -10 if the lock file cannot be opened. A flock error is NON-fatal (some 44// filesystems lack locks) -- it is best-effort and ws_put_p still runs (O_APPEND/tmp+rename remain). 45func ws_put_locked(prefix: *u8, key: *u8, val: *u8) -> i64 { 46 let lp: *u8 = sys_mmap(512) 47 wc_lockpath(prefix, lp) 48 let lfd: i64 = sys_openat_append(lp, 0x1a4) // O_WRONLY|O_CREAT|O_APPEND, mode 0644 49 if lfd < 0 { return 0 - 10 } 50 sys_flock(lfd, SYS_LOCK_EX) 51 let rc: i64 = ws_put_p(prefix, key, val) 52 sys_flock(lfd, SYS_LOCK_UN) 53 sys_close(lfd) 54 return rc 55}