querysub 0.710.0 → 0.711.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.710.0",
3
+ "version": "0.711.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",
@@ -79,7 +79,7 @@
79
79
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
80
80
  "pako": "^2.1.0",
81
81
  "peggy": "^5.0.6",
82
- "sliftutils": "^1.7.149",
82
+ "sliftutils": "^1.8.20",
83
83
  "socket-function": "^1.3.1",
84
84
  "terser": "^5.31.0",
85
85
  "typenode": "^6.6.1",
@@ -9,7 +9,8 @@ import { runPromise } from "../functional/runCommand";
9
9
  import { getExternalIP } from "socket-function/src/networking";
10
10
  import { errorToUndefined, errorToUndefinedSilent } from "../errors";
11
11
  import { getDomain } from "../config";
12
- import { formatTime } from "socket-function/src/formatting/format";
12
+ import { formatDateTime, formatTime } from "socket-function/src/formatting/format";
13
+ import { httpsRequest, HttpsResponseInfo } from "socket-function/src/https";
13
14
  import { sort, timeInMinute, timeInSecond, watchSlowPromise } from "socket-function/src/misc";
14
15
  import { isDefined, setPreciseTimeout } from "../misc";
15
16
  import { logLoadTime } from "../logModuleLoadTimes";
@@ -247,6 +248,30 @@ function checkGitSyncedAndClean(gitFolder: string, gitRef: string): Promise<bool
247
248
  return cached;
248
249
  }
249
250
 
251
+ async function recordMissingDependency(config: { gitFolder: string; screenName: string; error: unknown }) {
252
+ let { gitFolder, screenName } = config;
253
+ let match = String((config.error as Error)?.stack || config.error).match(/(https?:\/\/\S+?\.tgz): Request failed "404 Not Found"/);
254
+ if (!match) return;
255
+ let failedUrl = new URL(match[1]);
256
+ let tarballUrl = NPM_REGISTRY_ORIGIN + failedUrl.pathname;
257
+ let outResponse: HttpsResponseInfo = { headers: {} };
258
+ try {
259
+ await httpsRequest(tarballUrl, undefined, "HEAD", false, { outResponse });
260
+ } catch { }
261
+ if (outResponse.statusCode !== 404) {
262
+ console.warn(`Yarn install for ${screenName} failed on ${match[1]} returning 404, but npm returned ${outResponse.statusCode} for ${tarballUrl}, so it is not missing and we are not backing off`);
263
+ return;
264
+ }
265
+ let failures = (missingDependencyRetries.get(gitFolder)?.failures || 0) + 1;
266
+ let nextAttemptTime = failures >= MISSING_DEPENDENCY_MAX_FAILURES && Number.POSITIVE_INFINITY || Date.now() + MISSING_DEPENDENCY_RETRY_DELAY;
267
+ missingDependencyRetries.set(gitFolder, { failures, nextAttemptTime, tarballUrl });
268
+ if (failures >= MISSING_DEPENDENCY_MAX_FAILURES) {
269
+ console.error(`Giving up yarn installing for ${screenName}, ${tarballUrl} is still missing from npm after ${failures} failed installs. Retrying only on a config change.`);
270
+ return;
271
+ }
272
+ console.warn(`Yarn install for ${screenName} failed because ${tarballUrl} is missing from npm (failure ${failures}/${MISSING_DEPENDENCY_MAX_FAILURES}), retrying at ${formatDateTime(nextAttemptTime)}`);
273
+ }
274
+
250
275
  async function getQuerysubVersionMismatch(gitFolder: string): Promise<string> {
251
276
  let packageJson = JSON.parse(await fs.promises.readFile(gitFolder + "package.json", "utf8")) as { dependencies?: { [name: string]: string } };
252
277
  let requested = packageJson.dependencies?.querysub;
@@ -322,6 +347,11 @@ const RESYNC_INTERVAL = timeInSecond * 30;
322
347
  const CONFIG_REFRESH_INTERVAL = RESYNC_INTERVAL * 6;
323
348
 
324
349
  const LAUNCH_VERIFY_DELAY = 2000;
350
+
351
+ const NPM_REGISTRY_ORIGIN = "https://registry.npmjs.org";
352
+ const MISSING_DEPENDENCY_RETRY_DELAY = timeInMinute * 5;
353
+ const MISSING_DEPENDENCY_MAX_FAILURES = 5;
354
+ let missingDependencyRetries = new Map<string, { failures: number; nextAttemptTime: number; tarballUrl: string }>();
325
355
  // 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.
326
356
  const START_TIME_TOLERANCE = timeInSecond * 5;
327
357
 
@@ -503,9 +533,22 @@ async function ensureScreen(config: {
503
533
  let afterGitRef = await getGitRefLive(gitFolder);
504
534
  let nodeModulesMissing = !await fsExistsAsync(gitFolder + "node_modules");
505
535
  let querysubMismatch = await getQuerysubVersionMismatch(gitFolder);
506
- if (afterGitRef !== prevGitRef || nodeModulesMissing || querysubMismatch) {
507
- console.log(green(`Yarn installing for ${magenta(screenName)} (ref ${prevGitRef} -> ${afterGitRef}, nodeModulesMissing=${nodeModulesMissing}, querysubMismatch=${querysubMismatch || "none"})`));
508
- await runPromise(`yarn install --mutex network`, { cwd: gitFolder });
536
+ let missingRetry = missingDependencyRetries.get(gitFolder);
537
+ if (afterGitRef !== prevGitRef || nodeModulesMissing || querysubMismatch || missingRetry) {
538
+ if (missingRetry && Date.now() < missingRetry.nextAttemptTime) {
539
+ throw new Error(`Not yarn installing for ${screenName}, ${missingRetry.tarballUrl} was missing from npm after ${missingRetry.failures} failed installs. ${missingRetry.failures >= MISSING_DEPENDENCY_MAX_FAILURES && "Gave up, waiting for a config change to retry" || `Next attempt at ${formatDateTime(missingRetry.nextAttemptTime)}`}`);
540
+ }
541
+ console.log(green(`Yarn installing for ${magenta(screenName)} (ref ${prevGitRef} -> ${afterGitRef}, nodeModulesMissing=${nodeModulesMissing}, querysubMismatch=${querysubMismatch || "none"}, missingDependencyFailures=${missingRetry?.failures || 0})`));
542
+ try {
543
+ await runPromise(`yarn install --mutex network`, { cwd: gitFolder });
544
+ } catch (e) {
545
+ await recordMissingDependency({ gitFolder, screenName, error: e });
546
+ throw e;
547
+ }
548
+ if (missingRetry) {
549
+ console.log(green(`Yarn install for ${magenta(screenName)} succeeded after ${missingRetry.failures} failures from ${missingRetry.tarballUrl} missing from npm`));
550
+ missingDependencyRetries.delete(gitFolder);
551
+ }
509
552
  }
510
553
  }
511
554
 
@@ -654,6 +697,10 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
654
697
  /** A resync that re-reads the configs, however recently they were last read - for when something tells us they changed. */
655
698
  async function resyncServicesWithFreshConfigs() {
656
699
  configRefreshRequested = true;
700
+ if (missingDependencyRetries.size) {
701
+ console.log(`Config changed, clearing the missing dependency backoff for ${Array.from(missingDependencyRetries.keys()).join(", ")}`);
702
+ missingDependencyRetries.clear();
703
+ }
657
704
  await resyncServices();
658
705
  }
659
706
  async function resyncServices() {