* feat(#453): add deterministic clock seam to lock modules Introduces get-shit-done/bin/lib/clock.cjs exporting realClock with now() (Date.now) and sleep() (Atomics.wait). acquireStateLock, writeStateMd, and readModifyWriteStateMd in state.cjs each accept an optional trailing clock param (default: realClock). withPlanningLock in planning-workspace.cjs gains the same seam. No production behavior change — all callers that omit the param continue to use realClock. Adds tests/helpers/clock.cjs (makeFakeClock) and tests/clock-seam.test.cjs with 20 deterministic in-process tests covering: lock serialization, timeout throw at maxWaitMs boundary, stale-lock takeover, lock released on error path, withPlanningLock timeout recovery, exit-cleanup integration, readModifyWriteStateMd call-site coverage (7 cmd*), and roadmap analyze behavioral assertion (50 phases, no elapsed-time gate). Deletes/converts per research verdicts: removes 11 source-grep/elapsed-time/ non-deterministic-concurrent tests across concurrency-safety.test.cjs, locking-bugs-1909-1916-1925-1927.test.cjs, and bug-1974-context-exhaustion- record.test.cjs. All deleted tests have deterministic replacements in clock-seam.test.cjs or surviving barrier-based tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#453): update module inventory for clock.cjs; make EEXIST-retry assertion behavioral Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#453): satisfy lint-tests — allow-test-rule annotation on readFileSync/includes runtime output check Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
42 lines
1.4 KiB
JavaScript
42 lines
1.4 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Deterministic clock seam for lock modules (issue #453).
|
|
*
|
|
* Production code uses `realClock` (the default). Test code passes in a
|
|
* `makeFakeClock()` instance to drive lock timing without real wall-clock
|
|
* waits or Atomics.wait calls.
|
|
*
|
|
* Both methods in realClock use exactly the same system primitives that
|
|
* acquireStateLock and withPlanningLock used inline before the seam was
|
|
* introduced:
|
|
* - now() → Date.now()
|
|
* - sleep() → Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
|
|
*/
|
|
|
|
// Module-level Atomics.wait buffer reused across every realClock.sleep() call.
|
|
// The buffer value is always 0 (never written), so reuse is semantically
|
|
// identical to allocating a fresh buffer each time.
|
|
const _realSleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
|
|
const realClock = {
|
|
/** Return current epoch milliseconds (same as the inline Date.now() calls in state.cjs). */
|
|
now() {
|
|
return Date.now();
|
|
},
|
|
|
|
/**
|
|
* Synchronous sleep via Atomics.wait.
|
|
* This is the identical primitive acquireStateLock and withPlanningLock used
|
|
* inline before the seam. Atomics.wait on a shared buffer that is never
|
|
* notified times out after exactly `ms` milliseconds without spinning the CPU.
|
|
*
|
|
* @param {number} ms - milliseconds to sleep
|
|
*/
|
|
sleep(ms) {
|
|
Atomics.wait(_realSleepBuf, 0, 0, ms);
|
|
},
|
|
};
|
|
|
|
module.exports = { realClock };
|