Loading
Commits on Source 13
-
cznic authored
-
cznic authored
-
cznic authored
-
cznic authored
-
cznic authored
-
cznic authored
-
cznic authored
-
cznic authored
___lock/___unlock emulated a C mutex using an atomic lock-word fast path plus a throwaway hand-off object stored in a map. When an unlocker took locksMu before a contending locker had registered its hand-off, ___unlock synthesized one and immediately discarded it; the waiter then blocked on a fresh, never-unlocked mutex forever, and the lock word was left non-zero, so every subsequent ___lock on that address also blocked. The process wedged permanently at zero CPU. The window is reachable through the exported API alone: localtime, localtime_r and mktime all take the single process-global timezone lock (_lock4) via __secs_to_zone on every call, so goroutines calling them concurrently can hit it. Reported against modernc.org/quickjs evaluating JS Date local-time getters. Replace the scheme with a per-address sync.Mutex, created lazily and reference-counted for cleanup. The opaque C lock word is no longer touched (nothing reads it outside these two functions); routing all mutual exclusion through the per-address mutex makes the hand-off race-free, so a release delivered before the waiter blocks is no longer lost. Add TestIssue51: a mutual-exclusion stress on ___lock/___unlock plus a concurrent localtime_r stress; both clean under -race. Fixes: #51 Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
cznic authored
-
cznic authored
-
cznic authored
The failure path re-read *ptr in a separate step instead of reporting the value observed during the failed comparison. C11 7.17.7.4 puts that update inside the atomic read-modify-write ("Atomically, compares ... and if false, updates the value in expected with the value pointed to by object"), and the hardware does exactly that: cmpxchg loads the actual value in the same instruction. If *ptr transiently reverted to the expected value inside the window between the failed CAS and the separate reload, the helper returned failure while writing the expected value back into *expected. Callers spinning on while (atomic_compare_exchange_strong(&lock, &zero, 1) != 0) ; then left the loop without having acquired anything, because that idiom exits precisely when the reported old value equals the expected one. Restore the invariant that a failure reports a witness != old by retrying while the observed value is still old. A strong CAS that failed against old means *p != old at that instant, so a witness equal to old is never a valid failure report; and if the value did revert, a cmpxchg issued at that moment would have succeeded, making the retry more faithful than the bogus report. Affects the lock-free Int32/Uint32/Int64/Uint64 helpers. The Int8/Int16 and __c11_atomic_compare_exchange_strong* variants read the witness under the same mutex that guards the compare and were already correct. Found via modernc.org/libquickjs CI builders wedging at 100% CPU for 19+ hours in test262's Atomics agent tests, which use exactly that spin idiom. Hang rates on a 2-CPU linux/386 box, 200 runs of a single test: 20/200 (BigInt64Array) and 16/200 (Int32Array) before, 0/200 after. TestIssue52/failure_witness fails on the old code (order 10^3 violations per 2000000 attempts on amd64, 19028/2000000 on 386) and passes on the new. Only the contended case can observe this: TestIssue52/uncontended_semantics passes even on the broken code, which is why it went unnoticed. Closes #52 Co-Authored-By:Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-
cznic authored
-
cznic authored
getdirentries(2) reports records of deleted files with d_fileno set to zero and readdir(3) must not return them. On FFS such a record survives an unlink whenever it is the first one in a directory block, so any directory larger than a single 512-byte block reported ghost entries for files that were already gone. That made Tcl's glob (via libtcl8.6) see deleted files, causing the modernc.org/libsqlite3 openbsd/amd64 test failures delete_db-1.3.1, delete_db-1.4.1, multiplex-2.4.5, multiplex-5.3.prep and quota-5.3.prep. The failures only showed up in full-suite runs, once the shared working directory had grown past one block. Also stop iterating on a malformed d_reclen rather than spinning on it, matching the checks in OpenBSD's _readdir_unlocked(). Co-Authored-By:Claude Opus 4.8 (1M context) <noreply@anthropic.com>