@codedology/code-forge 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -300,11 +300,13 @@ export function clearDone({ repoRoot, run, block, file, tree = null }) {
300
300
  * A crash between the drop and the caller's enqueue leaves a re-runnable ticket with no marker:
301
301
  * harmless — its ticket file is pending, and the next `review-file` queues it anyway. Synchronous.
302
302
  * @param {string} repoRoot @param {string} ticket @param {Buffer} seen
303
- * @param {{link?: (from: string, to: string) => void, read?: (file: string) => Buffer, rename?: (from: string, to: string) => void, remove?: (file: string) => void, write?: (file: string, text: string) => void}} [io] - for
303
+ * `once` records the join's one retry before releasing the claim. An existing retry marker
304
+ * keeps the result, including when another join has already finished the rerun.
305
+ * @param {{once?: boolean, link?: (from: string, to: string) => void, read?: (file: string) => Buffer, rename?: (from: string, to: string) => void, remove?: (file: string) => void, write?: (file: string, text: string) => void}} [io] - for
304
306
  * tests (default `linkSync`, `readFileSync`, `renameSync`, `rmSync` with force, `writeFileSync`
305
307
  * with flag `wx` and mode 0o600). A claim that cannot be read is restored the same way and
306
308
  * refused `fresh-restore-failed`.
307
- * @returns {'dropped' | 'gone' | 'changed'}
309
+ * @returns {'dropped' | 'gone' | 'changed' | 'kept'}
308
310
  * @throws {WorkerError} `fresh-claim-failed` (the claim could not be taken, or not removed after
309
311
  * the drop; nothing was queued) or `fresh-restore-failed` (the message says which state was left:
310
312
  * put back, a newer marker, a refused marker, the claim kept, or nothing). A message names an
@@ -312,6 +314,8 @@ export function clearDone({ repoRoot, run, block, file, tree = null }) {
312
314
  */
313
315
  export function dropDoneIf(repoRoot, ticket, seen, io = {}) {
314
316
  const marker = path.join(queueDir(repoRoot), `${assertTicketId(ticket)}.done`);
317
+ const retried = path.join(queueDir(repoRoot), `${ticket}.retried`);
318
+ if (io.once && existsSync(retried)) return 'kept';
315
319
  const claim = `${marker}.${process.pid}.${randomBytes(4).toString('hex')}.fresh`;
316
320
  const remove = io.remove ?? ((/** @type {string} */ file) => rmSync(file, { force: true }));
317
321
  try {
@@ -329,6 +333,16 @@ export function dropDoneIf(repoRoot, ticket, seen, io = {}) {
329
333
  } catch (err) {
330
334
  readFailed = codeOf(err);
331
335
  }
336
+ let retryFailed = null;
337
+ if (same && io.once) {
338
+ try {
339
+ writeFileSync(retried, `${ticket}\n`, { flag: 'wx', mode: 0o600 });
340
+ } catch (err) {
341
+ // Restore the claim before keeping or refusing it. Only one join can spend the retry.
342
+ retryFailed = codeOf(err);
343
+ same = false;
344
+ }
345
+ }
332
346
  if (same) {
333
347
  try {
334
348
  remove(claim);
@@ -355,6 +369,8 @@ export function dropDoneIf(repoRoot, ticket, seen, io = {}) {
355
369
  // the marker is back (or a newer one won), so the ticket is done; `clearDone` removes the claim
356
370
  }
357
371
  if (readFailed !== null) throw new WorkerError('fresh-restore-failed', `${unread}; ${linkFailed === 'EEXIST' ? 'a newer marker is in place' : 'it was put back'}; nothing was queued`);
372
+ if (retryFailed === 'EEXIST') return 'kept';
373
+ if (retryFailed !== null) throw new WorkerError('fresh-claim-failed', `the retry marker of ticket ${ticket} could not be written (${retryFailed}); the done marker was restored; nothing was queued`);
358
374
  return 'changed';
359
375
  }
360
376