querysub 0.437.0 → 0.439.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.
Files changed (81) hide show
  1. package/.eslintrc.js +50 -50
  2. package/bin/deploy.js +0 -0
  3. package/bin/function.js +0 -0
  4. package/bin/server.js +0 -0
  5. package/costsBenefits.txt +115 -115
  6. package/deploy.ts +2 -2
  7. package/package.json +2 -2
  8. package/spec.txt +1192 -1192
  9. package/src/-a-archives/archives.ts +202 -202
  10. package/src/-a-archives/archivesDisk.ts +454 -454
  11. package/src/-a-auth/certs.ts +540 -540
  12. package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
  13. package/src/-b-authorities/dnsAuthority.ts +138 -138
  14. package/src/-c-identity/IdentityController.ts +258 -258
  15. package/src/-d-trust/NetworkTrust2.ts +180 -180
  16. package/src/-e-certs/EdgeCertController.ts +252 -252
  17. package/src/-e-certs/certAuthority.ts +201 -201
  18. package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
  19. package/src/-g-core-values/NodeCapabilities.ts +200 -200
  20. package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
  21. package/src/0-path-value-core/PathValueCommitter.ts +468 -468
  22. package/src/0-path-value-core/PathValueController.ts +0 -2
  23. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +37 -1
  24. package/src/0-path-value-core/pathValueCore.ts +12 -0
  25. package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
  26. package/src/2-proxy/TransactionDelayer.ts +94 -94
  27. package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
  28. package/src/2-proxy/pathValueProxy.ts +159 -159
  29. package/src/3-path-functions/PathFunctionRunner.ts +24 -13
  30. package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
  31. package/src/3-path-functions/pathFunctionLoader.ts +516 -516
  32. package/src/3-path-functions/tests/rejectTest.ts +76 -76
  33. package/src/4-deploy/deployCheck.ts +6 -6
  34. package/src/4-dom/css.tsx +29 -29
  35. package/src/4-dom/cssTypes.d.ts +211 -211
  36. package/src/4-dom/qreact.tsx +2799 -2799
  37. package/src/4-dom/qreactTest.tsx +410 -410
  38. package/src/4-querysub/permissions.ts +335 -335
  39. package/src/4-querysub/querysubPrediction.ts +483 -483
  40. package/src/5-diagnostics/qreactDebug.tsx +400 -346
  41. package/src/TestController.ts +34 -34
  42. package/src/bits.ts +104 -104
  43. package/src/buffers.ts +69 -69
  44. package/src/diagnostics/ActionsHistory.ts +57 -57
  45. package/src/diagnostics/PathDistributionInfo.tsx +9 -1
  46. package/src/diagnostics/listenOnDebugger.ts +71 -71
  47. package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +1 -1
  48. package/src/diagnostics/logs/diskLogger.ts +6 -0
  49. package/src/diagnostics/misc-pages/SnapshotViewer.tsx +78 -1
  50. package/src/diagnostics/periodic.ts +111 -111
  51. package/src/diagnostics/trackResources.ts +91 -91
  52. package/src/diagnostics/watchdog.ts +120 -120
  53. package/src/errors.ts +133 -133
  54. package/src/forceProduction.ts +2 -2
  55. package/src/fs.ts +80 -80
  56. package/src/functional/diff.ts +857 -857
  57. package/src/functional/promiseCache.ts +78 -78
  58. package/src/functional/random.ts +8 -8
  59. package/src/functional/stats.ts +60 -60
  60. package/src/heapDumps.ts +665 -665
  61. package/src/https.ts +1 -1
  62. package/src/library-components/AspectSizedComponent.tsx +87 -87
  63. package/src/library-components/ButtonSelector.tsx +64 -64
  64. package/src/library-components/DropdownCustom.tsx +150 -150
  65. package/src/library-components/DropdownSelector.tsx +31 -31
  66. package/src/library-components/InlinePopup.tsx +66 -66
  67. package/src/library-components/uncaughtToast.tsx +2 -0
  68. package/src/misc/color.ts +29 -29
  69. package/src/misc/hash.ts +83 -83
  70. package/src/misc/ipPong.js +13 -13
  71. package/src/misc/networking.ts +1 -1
  72. package/src/misc/random.ts +44 -44
  73. package/src/misc.ts +196 -196
  74. package/src/path.ts +255 -255
  75. package/src/persistentLocalStore.ts +41 -41
  76. package/src/promise.ts +14 -14
  77. package/src/storage/fileSystemPointer.ts +71 -71
  78. package/src/test/heapProcess.ts +35 -35
  79. package/src/zip.ts +15 -15
  80. package/tsconfig.json +26 -26
  81. package/yarnSpec.txt +56 -56
@@ -1,72 +1,72 @@
1
- import { isNode, list } from "socket-function/src/misc";
2
- import inspector from "inspector";
3
- import fs from "fs";
4
- import { getSubFolder } from "../fs";
5
- import { SocketFunction } from "socket-function/SocketFunction";
6
- import debugbreak from "debugbreak";
7
- import http from "http";
8
- import { lazy } from "socket-function/src/caching";
9
-
10
- let devToolsUrl = "";
11
-
12
- const listenOnDebugger = lazy(function listenOnDebugger() {
13
- if (devToolsUrl) return;
14
- if (!isNode()) return;
15
- // IMPORTANT! NEVER use the built-in port. This adds a tiny bit of security. Not much,
16
- // as port scanning is easier (there are only 65K ports!), but... at least port scanning
17
- // is loud, so if a site port scans all of it's users it will probably get flagged by
18
- // chrome fairly quickly as being malicious.
19
- let url = baseGetInspectorUrl();
20
-
21
- devToolsUrl = `devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&${url.replace("://", "=")}`;
22
- devToolsUrl = devToolsUrl.replace("devtools://devtools/bundled", "https://notdevtools.com/devtools");
23
- console.log(`Debugger listening on ${devToolsUrl}`);
24
-
25
- const ext = ".url";
26
- const debuggerFolder = getSubFolder("debugger");
27
- let otherDebuggers = fs.readdirSync(debuggerFolder);
28
- otherDebuggers = otherDebuggers.filter(x => x.endsWith(ext));
29
- // Delete all files for processes that no longer exist
30
- for (let other of otherDebuggers) {
31
- let pid = +other.split("-")[0];
32
- let ppid = +other.split("-")[1];
33
- if (!doesProcessExist(pid) || !doesProcessExist(ppid)) {
34
- try {
35
- fs.unlinkSync(debuggerFolder + other);
36
- } catch { }
37
- }
38
- }
39
- });
40
- export function getDebuggerUrl() {
41
- // NOTE: Very unfortunate, as this means that if a developer is debugging a server for a bit,
42
- // the inspector is left open. It's kind of hard to tell when they are done though.
43
- // - This is still somewhat secure, as only localhost can connect, but... having local network
44
- // accessing turning into remote execution access isn't great (especially for developers,
45
- // who could then get pwned just by clicking on a link).
46
- // TODO: The security of this can be improved, see NodeViewer.tsx:getExternalInspectURL
47
- listenOnDebugger();
48
- return devToolsUrl;
49
- }
50
- function doesProcessExist(pid: number) {
51
- try {
52
- process.kill(pid, 0);
53
- return true;
54
- } catch {
55
- return false;
56
- }
57
- }
58
- function baseGetInspectorUrl() {
59
- function getNextPort() {
60
- return 49152 + ~~((65535 - 49152) * Math.random());
61
- }
62
-
63
- while (true) {
64
- let url = inspector.url();
65
- if (url) return url;
66
- try {
67
- inspector.open(getNextPort());
68
- } catch {
69
- continue;
70
- }
71
- }
1
+ import { isNode, list } from "socket-function/src/misc";
2
+ import inspector from "inspector";
3
+ import fs from "fs";
4
+ import { getSubFolder } from "../fs";
5
+ import { SocketFunction } from "socket-function/SocketFunction";
6
+ import debugbreak from "debugbreak";
7
+ import http from "http";
8
+ import { lazy } from "socket-function/src/caching";
9
+
10
+ let devToolsUrl = "";
11
+
12
+ const listenOnDebugger = lazy(function listenOnDebugger() {
13
+ if (devToolsUrl) return;
14
+ if (!isNode()) return;
15
+ // IMPORTANT! NEVER use the built-in port. This adds a tiny bit of security. Not much,
16
+ // as port scanning is easier (there are only 65K ports!), but... at least port scanning
17
+ // is loud, so if a site port scans all of it's users it will probably get flagged by
18
+ // chrome fairly quickly as being malicious.
19
+ let url = baseGetInspectorUrl();
20
+
21
+ devToolsUrl = `devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&${url.replace("://", "=")}`;
22
+ devToolsUrl = devToolsUrl.replace("devtools://devtools/bundled", "https://notdevtools.com/devtools");
23
+ console.log(`Debugger listening on ${devToolsUrl}`);
24
+
25
+ const ext = ".url";
26
+ const debuggerFolder = getSubFolder("debugger");
27
+ let otherDebuggers = fs.readdirSync(debuggerFolder);
28
+ otherDebuggers = otherDebuggers.filter(x => x.endsWith(ext));
29
+ // Delete all files for processes that no longer exist
30
+ for (let other of otherDebuggers) {
31
+ let pid = +other.split("-")[0];
32
+ let ppid = +other.split("-")[1];
33
+ if (!doesProcessExist(pid) || !doesProcessExist(ppid)) {
34
+ try {
35
+ fs.unlinkSync(debuggerFolder + other);
36
+ } catch { }
37
+ }
38
+ }
39
+ });
40
+ export function getDebuggerUrl() {
41
+ // NOTE: Very unfortunate, as this means that if a developer is debugging a server for a bit,
42
+ // the inspector is left open. It's kind of hard to tell when they are done though.
43
+ // - This is still somewhat secure, as only localhost can connect, but... having local network
44
+ // accessing turning into remote execution access isn't great (especially for developers,
45
+ // who could then get pwned just by clicking on a link).
46
+ // TODO: The security of this can be improved, see NodeViewer.tsx:getExternalInspectURL
47
+ listenOnDebugger();
48
+ return devToolsUrl;
49
+ }
50
+ function doesProcessExist(pid: number) {
51
+ try {
52
+ process.kill(pid, 0);
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+ function baseGetInspectorUrl() {
59
+ function getNextPort() {
60
+ return 49152 + ~~((65535 - 49152) * Math.random());
61
+ }
62
+
63
+ while (true) {
64
+ let url = inspector.url();
65
+ if (url) return url;
66
+ try {
67
+ inspector.open(getNextPort());
68
+ } catch {
69
+ continue;
70
+ }
71
+ }
72
72
  }
@@ -276,7 +276,7 @@ export class BufferUnitIndex {
276
276
  // Only inaccurate if unique units approaches or exceeds 65k
277
277
  return uniqueCount * 2;
278
278
  }
279
- }, `collectUniqueUnits`);
279
+ }, `estimatedUniqueUnits`);
280
280
 
281
281
  // Step 3: Calculate hash table size and mask
282
282
  const { hashTableCapacity, mask } = measureBlock(() => {
@@ -21,6 +21,11 @@ if (isNode()) {
21
21
  }));
22
22
  }
23
23
 
24
+ let loggingToDiskDisabled = false;
25
+ export function disableLoggingToDisk() {
26
+ loggingToDiskDisabled = true;
27
+ }
28
+
24
29
  // NOTE: When logging we spread objects. If we encounter strings, we set the field `param${index}`
25
30
  export type LogDatum = Record<string, unknown> & {
26
31
  time: number;
@@ -109,6 +114,7 @@ const logDiskDontShim = logDisk;
109
114
  /** @deprecated, Don't call this directly, call console info instead, which our shim will prevent from logging to the console, but it will still call logDisk. */
110
115
  export function logDisk(type: "log" | "warn" | "info" | "error", ...args: unknown[]) {
111
116
  if (!isNode()) return;
117
+ if (loggingToDiskDisabled) return;
112
118
  try {
113
119
  if (args.length === 0) return;
114
120
  let logType = args.find(x => typeof x === "string") as string | undefined;
@@ -1,6 +1,6 @@
1
1
  module.allowclient = true;
2
2
 
3
- import { ArchiveSnapshotOverview, ArchiveSnapshotRead, getSnapshot, getSnapshotList, loadSnapshot, saveSnapshot } from "../../0-path-value-core/archiveLocks/archiveSnapshots";
3
+ import { ArchiveSnapshotOverview, ArchiveSnapshotRead, downloadSnapshot, getSnapshot, getSnapshotList, loadSnapshot, saveSnapshot, uploadSnapshot } from "../../0-path-value-core/archiveLocks/archiveSnapshots";
4
4
  import { qreact } from "../../4-dom/qreact";
5
5
  import { SocketFunction } from "socket-function/SocketFunction";
6
6
  import { getBrowserUrlNode } from "../../-f-node-discovery/NodeDiscovery";
@@ -30,6 +30,8 @@ export class SnapshotViewer extends qreact.Component {
30
30
  // file => true
31
31
  expanded: t.lookup(t.boolean),
32
32
  saving: t.boolean(false),
33
+ uploading: t.boolean(false),
34
+ downloading: t.lookup(t.boolean),
33
35
  });
34
36
  render() {
35
37
  let controller = SnapshotViewerSynced(getBrowserUrlNode());
@@ -40,6 +42,11 @@ export class SnapshotViewer extends qreact.Component {
40
42
  return (
41
43
  <div class={css.pad2(10).vbox(10)}>
42
44
  <h1>Snapshots are taken before every change. "zombie" means the file exists, but it has no confirm (so it will be ignored).</h1>
45
+ <div class={css.hbox(10)}>
46
+ <Button onClick={() => triggerSnapshotUpload(this)}>
47
+ {this.state.uploading && "Uploading..." || "Upload Snapshot"}
48
+ </Button>
49
+ </div>
43
50
  <Table
44
51
  rows={snapshotList}
45
52
  columns={{
@@ -68,6 +75,9 @@ export class SnapshotViewer extends qreact.Component {
68
75
  {this.state.saving ? "Saving..." : "Save"}
69
76
  </Button>
70
77
  }
78
+ <Button onClick={() => downloadSnapshotFile(this, file)}>
79
+ {this.state.downloading[file] && "Downloading..." || "Download"}
80
+ </Button>
71
81
  </div>;
72
82
  }
73
83
  }
@@ -142,6 +152,61 @@ export class SnapshotViewer extends qreact.Component {
142
152
  }
143
153
  }
144
154
 
155
+ async function downloadSnapshotFile(component: SnapshotViewer, file: string) {
156
+ if (component.state.downloading[file]) return;
157
+ Querysub.localCommit(() => {
158
+ component.state.downloading[file] = true;
159
+ });
160
+ try {
161
+ let buffer = await SnapshotViewerController.nodes[getBrowserUrlNode()].downloadSnapshot(file);
162
+ let blob = new Blob([buffer]);
163
+ let url = URL.createObjectURL(blob);
164
+ let anchor = document.createElement("a");
165
+ anchor.href = url;
166
+ anchor.download = file + ".cborx";
167
+ if (file === "live") {
168
+ anchor.download = `${formatDateTime(Date.now())}.cborx`;
169
+ }
170
+ document.body.appendChild(anchor);
171
+ anchor.click();
172
+ document.body.removeChild(anchor);
173
+ URL.revokeObjectURL(url);
174
+ } catch (e: any) {
175
+ console.error(red(`Failed to download snapshot ${file}: ${e.stack}`));
176
+ } finally {
177
+ Querysub.localCommit(() => {
178
+ delete component.state.downloading[file];
179
+ });
180
+ }
181
+ }
182
+
183
+ async function triggerSnapshotUpload(component: SnapshotViewer) {
184
+ if (component.state.uploading) return;
185
+ let input = document.createElement("input");
186
+ input.type = "file";
187
+ input.accept = ".cborx";
188
+ input.onchange = async () => {
189
+ let selected = input.files?.[0];
190
+ if (!selected) return;
191
+ Querysub.localCommit(() => {
192
+ component.state.uploading = true;
193
+ });
194
+ try {
195
+ let arrayBuffer = await selected.arrayBuffer();
196
+ let buffer = Buffer.from(arrayBuffer);
197
+ await SnapshotViewerController.nodes[getBrowserUrlNode()].uploadSnapshot(buffer);
198
+ console.log(green("Uploaded snapshot"));
199
+ } catch (e: any) {
200
+ console.error(red(`Failed to upload snapshot: ${e.stack}`));
201
+ } finally {
202
+ Querysub.localCommit(() => {
203
+ component.state.uploading = false;
204
+ });
205
+ }
206
+ };
207
+ input.click();
208
+ }
209
+
145
210
  async function seriousConfirm(config: {
146
211
  message: string;
147
212
  }): Promise<boolean> {
@@ -192,6 +257,14 @@ class SnapshotViewerControllerBase {
192
257
  let files = await getSnapshot("live");
193
258
  await saveSnapshot({ files: files.files.map(x => x.file) });
194
259
  }
260
+
261
+ public async downloadSnapshot(snapshotFile: string | "live"): Promise<Buffer> {
262
+ return await downloadSnapshot(snapshotFile);
263
+ }
264
+
265
+ public async uploadSnapshot(buffer: Buffer): Promise<ArchiveSnapshotOverview> {
266
+ return await uploadSnapshot(buffer);
267
+ }
195
268
  }
196
269
 
197
270
  export const SnapshotViewerController = SocketFunction.register(
@@ -202,6 +275,9 @@ export const SnapshotViewerController = SocketFunction.register(
202
275
  loadSnapshot: {},
203
276
  getSnapshot: {},
204
277
  saveLiveSnapshot: {},
278
+ downloadSnapshot: {},
279
+ uploadSnapshot: {},
280
+ example: {}
205
281
  }),
206
282
  () => ({
207
283
  hooks: [assertIsManagementUser],
@@ -217,5 +293,6 @@ const SnapshotViewerSynced = getSyncedController(SnapshotViewerController, {
217
293
  },
218
294
  writes: {
219
295
  saveLiveSnapshot: ["snapshots"],
296
+ uploadSnapshot: ["snapshots"],
220
297
  }
221
298
  });
@@ -1,112 +1,112 @@
1
- import { delay, runInfinitePoll, runInfinitePollCallAtStart, shutdownPolling } from "socket-function/src/batching";
2
- import { isNode, timeInMinute } from "socket-function/src/misc";
3
- import { logErrors, timeoutToError } from "../errors";
4
- import debugbreak from "debugbreak";
5
- import { nodeDiscoveryShutdown } from "../-f-node-discovery/NodeDiscovery";
6
- import { authorityStorage } from "../0-path-value-core/pathValueCore";
7
- import { red } from "socket-function/src/formatting/logColors";
8
-
9
- // Import querysub, so all the hooks we use will exist at some point
10
- setImmediate(async () => {
11
- await import("../4-querysub/QuerysubController");
12
- });
13
-
14
- let periodicFncs: (() => void)[] = [];
15
- export function registerPeriodic(fnc: () => void) {
16
- periodicFncs.push(fnc);
17
- }
18
-
19
- let shutdownHandlers: (() => Promise<void>)[] = [];
20
- // NOTE: Won't be always called, but at least makes it possible to gracefully shutdown.
21
- export function registerShutdownHandler(fnc: () => Promise<void>) {
22
- shutdownHandlers.push(fnc);
23
- }
24
- let preshutdownHandlers: (() => Promise<void>)[] = [];
25
- export function registerPreshutdownHandler(fnc: () => Promise<void>) {
26
- preshutdownHandlers.push(fnc);
27
- }
28
-
29
- function logAll() {
30
- for (let fnc of periodicFncs) {
31
- fnc();
32
- }
33
- }
34
-
35
- logErrors(runInfinitePollCallAtStart(timeInMinute * 5, logAll));
36
-
37
- let shuttingDown = false;
38
- export async function shutdown() {
39
- if (shuttingDown) {
40
- return;
41
- }
42
- console.log(red("Starting shutdown"));
43
- shuttingDown = true;
44
- const { authorityStorage } = await import("../0-path-value-core/pathValueCore");
45
- try {
46
- await Promise.allSettled([
47
- ...preshutdownHandlers,
48
- ].map(fnc => timeoutToError(timeInMinute, fnc(), () => new Error(`Preshutdown handler ${fnc.name} timed out`))));
49
- } catch (e) {
50
- console.log(`Error on preshutdown handlers`, e);
51
- }
52
- try {
53
- await Promise.allSettled([
54
- function authorityStorageShutdown() { return authorityStorage.onShutdown(); },
55
- nodeDiscoveryShutdown,
56
- shutdownPolling,
57
- ...shutdownHandlers,
58
- ].map(fnc => timeoutToError(timeInMinute, fnc(), () => new Error(`Shutdown handler ${fnc.name} timed out`))));
59
- } catch (e) {
60
- console.log("Error on shutdown", e);
61
- }
62
- // Wait to allow any logged errors to hopefully be written somewhere?
63
- await delay(2000);
64
- process.exit();
65
- }
66
-
67
- // IMPORTANT! Yarn detaches the processes, so they keep running when you ctrl+c, even though the shell shows back up. We can fix this by using `node -r ./node_modules/typenode/index.js ./test.ts`. However, it's probably fine, as we still run the shutdown code, it's just that the manager doesn't know if we've shutdown or not.
68
- if (isNode()) {
69
- let lineBuffer = "";
70
- process.stdin.on("data", data => {
71
- lineBuffer += data.toString();
72
- let lines = lineBuffer.split("\r");
73
- lineBuffer = lines.pop()!;
74
- for (let line of lines) {
75
- if (line === "SHUTDOWN_NOW_MULTIRUN") {
76
- logErrors(shutdown());
77
- }
78
- }
79
-
80
- if (data.toString().includes("\r")) {
81
- logAll();
82
- }
83
- });
84
-
85
-
86
- // NOTE: This extra code is required to actual capture ctrl+c
87
- if (process.platform === "win32") {
88
- var rl = require("readline").createInterface({
89
- input: process.stdin,
90
- output: process.stdout
91
- });
92
-
93
- rl.on("SIGINT", function () {
94
- process.emit("SIGINT");
95
- });
96
- }
97
- let killCount = 0;
98
- function doShutdown() {
99
- killCount++;
100
- console.log(`Caught interrupt signal. Attempt number ${killCount}`);
101
- if (killCount >= 3) {
102
- console.log("Force exit");
103
- process.exit();
104
- }
105
-
106
- logErrors(shutdown());
107
- }
108
- process.on("SIGINT", doShutdown);
109
-
110
- }
111
- (globalThis as any).logAll = logAll;
1
+ import { delay, runInfinitePoll, runInfinitePollCallAtStart, shutdownPolling } from "socket-function/src/batching";
2
+ import { isNode, timeInMinute } from "socket-function/src/misc";
3
+ import { logErrors, timeoutToError } from "../errors";
4
+ import debugbreak from "debugbreak";
5
+ import { nodeDiscoveryShutdown } from "../-f-node-discovery/NodeDiscovery";
6
+ import { authorityStorage } from "../0-path-value-core/pathValueCore";
7
+ import { red } from "socket-function/src/formatting/logColors";
8
+
9
+ // Import querysub, so all the hooks we use will exist at some point
10
+ setImmediate(async () => {
11
+ await import("../4-querysub/QuerysubController");
12
+ });
13
+
14
+ let periodicFncs: (() => void)[] = [];
15
+ export function registerPeriodic(fnc: () => void) {
16
+ periodicFncs.push(fnc);
17
+ }
18
+
19
+ let shutdownHandlers: (() => Promise<void>)[] = [];
20
+ // NOTE: Won't be always called, but at least makes it possible to gracefully shutdown.
21
+ export function registerShutdownHandler(fnc: () => Promise<void>) {
22
+ shutdownHandlers.push(fnc);
23
+ }
24
+ let preshutdownHandlers: (() => Promise<void>)[] = [];
25
+ export function registerPreshutdownHandler(fnc: () => Promise<void>) {
26
+ preshutdownHandlers.push(fnc);
27
+ }
28
+
29
+ function logAll() {
30
+ for (let fnc of periodicFncs) {
31
+ fnc();
32
+ }
33
+ }
34
+
35
+ logErrors(runInfinitePollCallAtStart(timeInMinute * 5, logAll));
36
+
37
+ let shuttingDown = false;
38
+ export async function shutdown() {
39
+ if (shuttingDown) {
40
+ return;
41
+ }
42
+ console.log(red("Starting shutdown"));
43
+ shuttingDown = true;
44
+ const { authorityStorage } = await import("../0-path-value-core/pathValueCore");
45
+ try {
46
+ await Promise.allSettled([
47
+ ...preshutdownHandlers,
48
+ ].map(fnc => timeoutToError(timeInMinute, fnc(), () => new Error(`Preshutdown handler ${fnc.name} timed out`))));
49
+ } catch (e) {
50
+ console.log(`Error on preshutdown handlers`, e);
51
+ }
52
+ try {
53
+ await Promise.allSettled([
54
+ function authorityStorageShutdown() { return authorityStorage.onShutdown(); },
55
+ nodeDiscoveryShutdown,
56
+ shutdownPolling,
57
+ ...shutdownHandlers,
58
+ ].map(fnc => timeoutToError(timeInMinute, fnc(), () => new Error(`Shutdown handler ${fnc.name} timed out`))));
59
+ } catch (e) {
60
+ console.log("Error on shutdown", e);
61
+ }
62
+ // Wait to allow any logged errors to hopefully be written somewhere?
63
+ await delay(2000);
64
+ process.exit();
65
+ }
66
+
67
+ // IMPORTANT! Yarn detaches the processes, so they keep running when you ctrl+c, even though the shell shows back up. We can fix this by using `node -r ./node_modules/typenode/index.js ./test.ts`. However, it's probably fine, as we still run the shutdown code, it's just that the manager doesn't know if we've shutdown or not.
68
+ if (isNode()) {
69
+ let lineBuffer = "";
70
+ process.stdin.on("data", data => {
71
+ lineBuffer += data.toString();
72
+ let lines = lineBuffer.split("\r");
73
+ lineBuffer = lines.pop()!;
74
+ for (let line of lines) {
75
+ if (line === "SHUTDOWN_NOW_MULTIRUN") {
76
+ logErrors(shutdown());
77
+ }
78
+ }
79
+
80
+ if (data.toString().includes("\r")) {
81
+ logAll();
82
+ }
83
+ });
84
+
85
+
86
+ // NOTE: This extra code is required to actual capture ctrl+c
87
+ if (process.platform === "win32") {
88
+ var rl = require("readline").createInterface({
89
+ input: process.stdin,
90
+ output: process.stdout
91
+ });
92
+
93
+ rl.on("SIGINT", function () {
94
+ process.emit("SIGINT");
95
+ });
96
+ }
97
+ let killCount = 0;
98
+ function doShutdown() {
99
+ killCount++;
100
+ console.log(`Caught interrupt signal. Attempt number ${killCount}`);
101
+ if (killCount >= 3) {
102
+ console.log("Force exit");
103
+ process.exit();
104
+ }
105
+
106
+ logErrors(shutdown());
107
+ }
108
+ process.on("SIGINT", doShutdown);
109
+
110
+ }
111
+ (globalThis as any).logAll = logAll;
112
112
  (globalThis as any).logNow = logAll;