querysub 0.580.0 → 0.582.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.580.0",
3
+ "version": "0.582.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -71,7 +71,7 @@
71
71
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
72
72
  "pako": "^2.1.0",
73
73
  "peggy": "^5.0.6",
74
- "sliftutils": "^1.7.50",
74
+ "sliftutils": "^1.7.51",
75
75
  "socket-function": "^1.2.26",
76
76
  "terser": "^5.31.0",
77
77
  "typenode": "^6.6.1",
@@ -28,7 +28,8 @@ const PAST_COLOR = { h: 0, s: 0, l: 90 };
28
28
  const FUTURE_COLOR = { h: 45, s: 80, l: 85 };
29
29
  // Windows are days or hours wide, so the countdown only has to be roughly right
30
30
  const TIME_REFRESH_INTERVAL = 60 * 1000;
31
- const WATCH_POLL_INTERVAL = 60 * 1000;
31
+ // Watching exists to see a config change as it happens - a deploy or a server disagreeing with the others - so it polls far faster than the window countdowns need to update
32
+ const WATCH_POLL_INTERVAL = 5 * 1000;
32
33
  const WATCH_ACTIVE_COLOR = { h: 195, s: 60, l: 85 };
33
34
  const WINDOW_HEADER_SIZE = 13;
34
35
  const WINDOW_TIME_SIZE = 14;
@@ -513,7 +514,8 @@ class RouteConfigGroupView extends qreact.Component<{ group: RouteConfigGroup }>
513
514
  class LiveWatchView extends qreact.Component<{ watchKey: string; watch: LiveWatch }> {
514
515
  render() {
515
516
  let { watchKey, watch } = this.props;
516
- let now = Querysub.timeDelayed(TIME_REFRESH_INTERVAL);
517
+ // Ticks with the polling, not with the window countdowns - "updated 1 minute ago" on something refreshed seconds ago reads as broken
518
+ let now = Querysub.timeDelayed(WATCH_POLL_INTERVAL);
517
519
  let sources = (watch.routing?.sources || []).map(normalizeSource);
518
520
  return <div className={css.vbox(10).fillWidth.pad2(10, 8).bord2(WATCH_ACTIVE_COLOR.h, WATCH_ACTIVE_COLOR.s, WATCH_ACTIVE_COLOR.l - 25)}>
519
521
  <div className={css.hbox(8).wrap.fontSize(WINDOW_HEADER_SIZE)}>
@@ -220,6 +220,11 @@ const ensureGitSynced = measureWrap(async function ensureGitSynced(config: {
220
220
  let launchesPerService = new Map<string, number>();
221
221
  let lastLaunchedTimePerService = new Map<string, number>();
222
222
 
223
+ // How often we check that everything that should be running still is. Cheap - it asks tmux and the local filesystem - so it runs often enough that a crashed process is noticed in seconds rather than minutes.
224
+ const RESYNC_INTERVAL = timeInSecond * 30;
225
+ // How often the configs themselves are re-read. Reading them hits backblaze, so this is the interval that has to stay conservative, and it is entirely independent of how often we check liveness.
226
+ const CONFIG_REFRESH_INTERVAL = RESYNC_INTERVAL * 6;
227
+
223
228
  const LAUNCH_VERIFY_DELAY = 2000;
224
229
  // How far a re-read start time may drift from the recorded one and still be the same process. Well under any interval a pid could be reused in, and wide enough for sources that only resolve to the second.
225
230
  const START_TIME_TOLERANCE = timeInSecond * 5;
@@ -448,10 +453,29 @@ function sameRestartParameters(prevParametersJSON: string, next: ServiceParamete
448
453
  }
449
454
  }
450
455
 
456
+ // The configs come from backblaze, and reading them is by far the most expensive thing a resync does - far too expensive to do every time we want to check that the processes are still up, which is the other, much cheaper half of a resync. So the configs are re-read on their own schedule and reused in between.
457
+ let cachedConfigs: ServiceConfig[] | undefined;
458
+ let cachedConfigsTime = 0;
459
+ // Set when something tells us the configs actually changed, which no interval can predict
460
+ let configRefreshRequested = false;
461
+ async function getResyncConfigs(): Promise<{ configs: ServiceConfig[]; refreshed: boolean }> {
462
+ let force = configRefreshRequested;
463
+ if (!force && cachedConfigs && Date.now() - cachedConfigsTime < CONFIG_REFRESH_INTERVAL) {
464
+ return { configs: cachedConfigs, refreshed: false };
465
+ }
466
+ // Cleared before the read, so a change arriving DURING it is not swallowed by our own success
467
+ configRefreshRequested = false;
468
+ cachedConfigs = await getEffectiveServiceConfigs();
469
+ cachedConfigsTime = Date.now();
470
+ return { configs: cachedConfigs, refreshed: true };
471
+ }
472
+
451
473
  const resyncServicesBase = runInSerial(measureWrap(async function resyncServices() {
452
- console.log(magenta("Resyncing services"));
453
474
  let machineId = getOwnMachineId(getDomain());
454
- let allConfigs = await getEffectiveServiceConfigs();
475
+ let { configs: allConfigs, refreshed } = await getResyncConfigs();
476
+ if (refreshed) {
477
+ console.log(magenta("Resyncing services, with freshly read configs"));
478
+ }
455
479
  let relevantConfigs = allConfigs
456
480
  .filter(config => getMachineIdList(getLiveServiceParameters(config)).includes(machineId) || getMachineIdList(config.parameters).includes(machineId) || config.oldParameters && getMachineIdList(config.oldParameters).includes(machineId))
457
481
  .filter(x => getLiveServiceParameters(x).deploy || x.parameters.deploy || x.oldParameters?.deploy);
@@ -500,11 +524,19 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
500
524
  }
501
525
 
502
526
  await machineInfos.set(machineId, machineInfo);
503
- console.log(`${magenta(`Resynced ${relevantConfigs.length} services`)}:\n${JSON.stringify(machineInfo, null, 2)}`);
527
+ // Only on the runs that read the configs. The rest are a liveness check that happens many times a minute, and dumping the whole machine info each time buries everything that actually matters.
528
+ if (refreshed) {
529
+ console.log(`${magenta(`Resynced ${relevantConfigs.length} services`)}:\n${JSON.stringify(machineInfo, null, 2)}`);
530
+ }
504
531
 
505
532
  // Don't resync TOO often, so always wait a bit
506
533
  await delay(5000);
507
534
  }));
535
+ /** A resync that re-reads the configs, however recently they were last read - for when something tells us they changed. */
536
+ async function resyncServicesWithFreshConfigs() {
537
+ configRefreshRequested = true;
538
+ await resyncServices();
539
+ }
508
540
  async function resyncServices() {
509
541
  try {
510
542
  await resyncServicesBase();
@@ -568,10 +600,10 @@ export async function machineApplyMain() {
568
600
 
569
601
 
570
602
  await Querysub.hostService("machine-apply");
571
- onServiceConfigChange(resyncServices);
603
+ onServiceConfigChange(resyncServicesWithFreshConfigs);
572
604
 
573
605
 
574
- runInfinitePoll(timeInMinute * 3, async () => {
606
+ runInfinitePoll(RESYNC_INTERVAL, async () => {
575
607
  //console.log(magenta(`Quick outdated check at ${new Date().toISOString()}`));
576
608
  // console.log(magenta("Likely outdated, resyncing now"));
577
609
  // NOTE: I'm not sure why we were hesitant to call this. It shouldn't be that slow...