atris 3.78.0 → 3.80.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.
package/lib/member-up.js CHANGED
@@ -184,6 +184,7 @@ function createMemberUp(deps, options = {}) {
184
184
  const max = Math.max(1, Number(options.max) || DEFAULT_MAX);
185
185
  const dryRun = options.dryRun === true;
186
186
  const print = deps.print || ((line) => console.log(line));
187
+ const reportError = deps.reportError || ((context, error) => console.error(`[member-up] ${context}:`, error));
187
188
  // only: when set, these members are the whole team for this supervisor.
188
189
  // Nobody else is started or counted, and unknown names are skipped.
189
190
  const only = Array.isArray(options.only) && options.only.length
@@ -319,6 +320,7 @@ function createMemberUp(deps, options = {}) {
319
320
  out = deps.requeueDropped();
320
321
  requeueFailSaid = false;
321
322
  } catch (error) {
323
+ reportError('could not check github merge line', error);
322
324
  if (!requeueFailSaid) print(`could not check github's merge line: ${oneLine(error && error.message || error)}`);
323
325
  requeueFailSaid = true;
324
326
  return;
@@ -336,6 +338,7 @@ function createMemberUp(deps, options = {}) {
336
338
  try {
337
339
  out = deps.writeNextJob(name, { dryRun });
338
340
  } catch (error) {
341
+ reportError(`${name} could not write its own next job`, error);
339
342
  if (!s.idle) print(`${name} could not write its own next job: ${oneLine(error && error.message || error)}`);
340
343
  return null;
341
344
  }
@@ -384,7 +387,13 @@ function createMemberUp(deps, options = {}) {
384
387
  if (working() + (dryRun ? started.length : 0) >= max) break;
385
388
  const s = seat(name);
386
389
  if (now < s.nextCheckAt) continue;
387
- const picked = deps.pickTask(name, taken);
390
+ let picked;
391
+ try {
392
+ picked = deps.pickTask(name, taken);
393
+ } catch (error) {
394
+ reportError(`${name} could not read the task queue`, error);
395
+ continue;
396
+ }
388
397
  let task = picked && hasRunnableCheck(picked) ? picked : null;
389
398
  if (!task) task = ownNextJob(name, s);
390
399
  if (!task) {
@@ -412,6 +421,7 @@ function createMemberUp(deps, options = {}) {
412
421
  record({ member: name, pid: child && child.pid ? child.pid : null });
413
422
  } catch (error) {
414
423
  running.delete(name);
424
+ reportError(`${name} failed to start`, error);
415
425
  print(`${name} failed to start: ${oneLine(error && error.message || error)}`);
416
426
  continue;
417
427
  }
@@ -419,7 +429,9 @@ function createMemberUp(deps, options = {}) {
419
429
  }
420
430
  try {
421
431
  otherKindHints(team, members.filter((m) => m && m.name && !m.parked && !m.human && !m.sleeping), taken);
422
- } catch {}
432
+ } catch (error) {
433
+ reportError('could not inspect waiting tasks', error);
434
+ }
423
435
  if (!dryRun && now - lastSummaryAt >= SUMMARY_MS) {
424
436
  lastSummaryAt = now;
425
437
  print(summary());
@@ -486,16 +498,41 @@ function parseMemberUpArgs(args = []) {
486
498
  return out;
487
499
  }
488
500
 
501
+ // Persistent watcher failures can recur every second. Record the first one,
502
+ // then preserve repeat counts in bounded summaries rather than filling disk.
503
+ function watchErrorReporter(report, now = Date.now, intervalMs = 60000) {
504
+ const failures = new Map();
505
+ function emitPending(entry) {
506
+ if (!entry.repeats) return;
507
+ report(entry.context, new Error(`${entry.repeats} repeated watcher errors: ${entry.message}`));
508
+ entry.repeats = 0;
509
+ entry.last = now();
510
+ }
511
+ return {
512
+ report(context, error) {
513
+ const message = String(error?.message || error);
514
+ const key = `${context}\0${error?.code || ''}\0${message}`;
515
+ let entry = failures.get(key);
516
+ if (!entry) { entry = { context, message, repeats: 0, last: now() }; failures.set(key, entry); report(context, error); return; }
517
+ entry.repeats += 1;
518
+ if (now() - entry.last >= intervalMs) emitPending(entry);
519
+ },
520
+ flush() { for (const entry of failures.values()) emitPending(entry); },
521
+ };
522
+ }
523
+
489
524
  // Runs checks every minute until stop() is called. `sleep(ms)` must resolve
490
525
  // after ms or as soon as the returned stop fires.
491
526
  function runMemberUp(deps, options = {}) {
492
527
  // Begin following before any child starts, so its first step cannot be lost.
493
528
  let follower;
529
+ const watchErrors = watchErrorReporter(deps.reportError || ((where, err) => console.error(`[member-up] ${where}:`, err)), deps.now);
494
530
  const up = createMemberUp({ ...deps, flushProgress: () => follower?.scan() }, options);
495
531
  if (!options.dryRun) follower = followBoard({
496
532
  dir: options.progressDir,
497
533
  pollMs: options.progressPollMs,
498
534
  orderByTime: true,
535
+ onError: (context, error) => watchErrors.report(context, error),
499
536
  emit: (event) => up.progress(event),
500
537
  });
501
538
  let stopped = false;
@@ -506,7 +543,11 @@ function runMemberUp(deps, options = {}) {
506
543
  }));
507
544
  const done = (async () => {
508
545
  while (!stopped) {
509
- up.check();
546
+ try {
547
+ up.check();
548
+ } catch (error) {
549
+ (deps.reportError || ((context, err) => console.error(`[member-up] ${context}:`, err)))('supervisor check failed', error);
550
+ }
510
551
  if (stopped) break;
511
552
  await sleep(options.checkMs || CHECK_MS);
512
553
  }
@@ -519,6 +560,7 @@ function runMemberUp(deps, options = {}) {
519
560
  stop() {
520
561
  stopped = true;
521
562
  follower?.stop();
563
+ watchErrors.flush();
522
564
  if (wake) wake();
523
565
  },
524
566
  };
@@ -540,4 +582,5 @@ module.exports = {
540
582
  withMaintenanceLock,
541
583
  parseMemberUpArgs,
542
584
  runMemberUp,
585
+ watchErrorReporter,
543
586
  };