Skip to content

Commit 5e9727d

Browse files
committed
fix(bin): let the re-key wait on a busy store as long as issue.mjs
The re-key is a CLI, but it took the issue lock with the request handler's budget of 50 rounds of 20ms. bin/issue.mjs waits 250. On a loaded runner, eight mints holding the lock in turn outlasted that second, and the re-key refused with exit 4. That failed "a re-key running beside live mints loses none of them" once on Ubuntu Node 22. It now waits 250 rounds. A new test holds the lock for 2s from another process: the old re-key refuses and the new one completes.
1 parent a584e16 commit 5e9727d

3 files changed

Lines changed: 36 additions & 2 deletions

File tree

‎bin/issue-rekey.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ function rekey() {
136136
let code;
137137
if (write) {
138138
try {
139-
code = withIssuesLock(rekey, { path: STORE() });
139+
code = withIssuesLock(rekey, { path: STORE(), attempts: 250 });
140140
} catch (e) {
141141
// Contention: "busy, nothing written" must never read as "ran and failed".
142142
console.error(`${e.message}\nnothing was written; the store is unchanged. Re-run when the other writer finishes.`);

‎bin/test/issue-store-concurrency.test.mjs‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,40 @@ test('a re-key running beside live mints loses none of them', async () => {
137137
}
138138
});
139139

140+
// The re-key is a CLI and waits on a busy store as long as bin/issue.mjs does: a request handler's
141+
// ~1s budget is shorter than eight mints can hold the lock on a loaded machine.
142+
test('a re-key waits out a writer that holds the lock longer than a request handler would', async () => {
143+
const dir = mkdtempSync(join(tmpdir(), 'cw-issues-rekey-wait-'));
144+
try {
145+
const store = join(dir, 'issues.json');
146+
legacyFixture(store, 20);
147+
const env = { ...process.env, CW_ISSUES: store, CW_ISSUE_ORG: 'PROBE', CW_VERDICT_DIR: dir };
148+
149+
// writeSync, not stdout.write: a pipe write on macOS is async and would not flush before the wait blocks
150+
const holder = spawn(process.execPath, ['--input-type=module', '-e', `
151+
import { writeSync } from 'node:fs';
152+
import { withIssuesLock } from './monitor/issue-store.mjs';
153+
withIssuesLock(() => {
154+
writeSync(1, 'held\\n');
155+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
156+
}, { path: process.env.CW_ISSUES });
157+
`], { cwd: CW, env, stdio: ['ignore', 'pipe', 'inherit'] });
158+
const holderDone = new Promise((ok) => holder.on('close', ok));
159+
await new Promise((ok, fail) => {
160+
holder.stdout.on('data', (d) => { if (String(d).includes('held')) ok(); });
161+
holder.on('close', (code) => fail(new Error(`the lock holder exited (${code}) before taking the lock`)));
162+
});
163+
164+
const rekey = await run([join(CW, 'bin', 'issue-rekey.mjs'), '--org', 'PROBE', '--write'], env);
165+
await holderDone;
166+
assert.equal(rekey.code, 0, `the re-key must outwait a 2s holder: ${rekey.err.trim()}`);
167+
assert.match(rekey.out, /wrote /);
168+
assert.deepEqual(Object.keys(readStore(store).issues).filter((k) => /^ISS-[0-9A-Z]{6}$/.test(k)), []);
169+
} finally {
170+
rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 50 });
171+
}
172+
});
173+
140174
// A lock a caller has to remember is a lock a caller will forget — so the store refuses an
141175
// unlocked save, turning a silent lost update into a loud refusal at the call site.
142176
test('saveIssues refuses to write when the caller does not hold the issue lock', () => {

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "commitwork",
3-
"version": "0.5.2",
3+
"version": "0.5.3",
44
"description": "Repository security scanner, local CI runner and scheduled monitor, with remediation and compliance-evidence tooling. Manifest-driven; runs against any git repository.",
55
"type": "module",
66
"bin": {

0 commit comments

Comments
 (0)