querysub 0.2.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 (169) hide show
  1. package/.dependency-cruiser.js +304 -0
  2. package/.eslintrc.js +51 -0
  3. package/.github/copilot-instructions.md +1 -0
  4. package/.vscode/settings.json +25 -0
  5. package/bin/deploy.js +4 -0
  6. package/bin/function.js +4 -0
  7. package/bin/server.js +4 -0
  8. package/costsBenefits.txt +112 -0
  9. package/deploy.ts +3 -0
  10. package/inject.ts +1 -0
  11. package/package.json +60 -0
  12. package/prompts.txt +54 -0
  13. package/spec.txt +820 -0
  14. package/src/-a-archives/archiveCache.ts +913 -0
  15. package/src/-a-archives/archives.ts +148 -0
  16. package/src/-a-archives/archivesBackBlaze.ts +792 -0
  17. package/src/-a-archives/archivesDisk.ts +418 -0
  18. package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
  19. package/src/-a-auth/certs.ts +517 -0
  20. package/src/-a-auth/der.ts +122 -0
  21. package/src/-a-auth/ed25519.ts +1015 -0
  22. package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
  23. package/src/-b-authorities/dnsAuthority.ts +203 -0
  24. package/src/-b-authorities/emailAuthority.ts +57 -0
  25. package/src/-c-identity/IdentityController.ts +200 -0
  26. package/src/-d-trust/NetworkTrust2.ts +150 -0
  27. package/src/-e-certs/EdgeCertController.ts +288 -0
  28. package/src/-e-certs/certAuthority.ts +192 -0
  29. package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
  30. package/src/-g-core-values/NodeCapabilities.ts +134 -0
  31. package/src/-g-core-values/oneTimeForward.ts +91 -0
  32. package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
  33. package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
  34. package/src/0-path-value-core/LoggingClient.tsx +24 -0
  35. package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
  36. package/src/0-path-value-core/PathController.ts +1 -0
  37. package/src/0-path-value-core/PathValueCommitter.ts +565 -0
  38. package/src/0-path-value-core/PathValueController.ts +231 -0
  39. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
  40. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
  41. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
  42. package/src/0-path-value-core/debugLogs.ts +90 -0
  43. package/src/0-path-value-core/pathValueArchives.ts +483 -0
  44. package/src/0-path-value-core/pathValueCore.ts +2217 -0
  45. package/src/1-path-client/RemoteWatcher.ts +558 -0
  46. package/src/1-path-client/pathValueClientWatcher.ts +702 -0
  47. package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
  48. package/src/2-proxy/archiveMoveHarness.ts +376 -0
  49. package/src/2-proxy/garbageCollection.ts +753 -0
  50. package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
  51. package/src/2-proxy/pathValueProxy.ts +139 -0
  52. package/src/2-proxy/schema2.ts +518 -0
  53. package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
  54. package/src/3-path-functions/PathFunctionRunner.ts +619 -0
  55. package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
  56. package/src/3-path-functions/deployBlock.ts +10 -0
  57. package/src/3-path-functions/deployCheck.ts +7 -0
  58. package/src/3-path-functions/deployMain.ts +160 -0
  59. package/src/3-path-functions/pathFunctionLoader.ts +282 -0
  60. package/src/3-path-functions/syncSchema.ts +475 -0
  61. package/src/3-path-functions/tests/functionsTest.ts +135 -0
  62. package/src/3-path-functions/tests/rejectTest.ts +77 -0
  63. package/src/4-dom/css.tsx +29 -0
  64. package/src/4-dom/cssTypes.d.ts +212 -0
  65. package/src/4-dom/qreact.tsx +2322 -0
  66. package/src/4-dom/qreactTest.tsx +417 -0
  67. package/src/4-querysub/Querysub.ts +877 -0
  68. package/src/4-querysub/QuerysubController.ts +620 -0
  69. package/src/4-querysub/copyEvent.ts +0 -0
  70. package/src/4-querysub/permissions.ts +289 -0
  71. package/src/4-querysub/permissionsShared.ts +1 -0
  72. package/src/4-querysub/querysubPrediction.ts +525 -0
  73. package/src/5-diagnostics/FullscreenModal.tsx +67 -0
  74. package/src/5-diagnostics/GenericFormat.tsx +165 -0
  75. package/src/5-diagnostics/Modal.tsx +79 -0
  76. package/src/5-diagnostics/Table.tsx +183 -0
  77. package/src/5-diagnostics/TimeGrouper.tsx +114 -0
  78. package/src/5-diagnostics/diskValueAudit.ts +216 -0
  79. package/src/5-diagnostics/memoryValueAudit.ts +442 -0
  80. package/src/5-diagnostics/nodeMetadata.ts +135 -0
  81. package/src/5-diagnostics/qreactDebug.tsx +309 -0
  82. package/src/5-diagnostics/shared.ts +26 -0
  83. package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
  84. package/src/TestController.ts +35 -0
  85. package/src/allowclient.flag +0 -0
  86. package/src/bits.ts +86 -0
  87. package/src/buffers.ts +69 -0
  88. package/src/config.ts +53 -0
  89. package/src/config2.ts +48 -0
  90. package/src/diagnostics/ActionsHistory.ts +56 -0
  91. package/src/diagnostics/NodeViewer.tsx +503 -0
  92. package/src/diagnostics/SizeLimiter.ts +62 -0
  93. package/src/diagnostics/TimeDebug.tsx +18 -0
  94. package/src/diagnostics/benchmark.ts +139 -0
  95. package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
  96. package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
  97. package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
  98. package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
  99. package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
  100. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
  101. package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
  102. package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
  103. package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
  104. package/src/diagnostics/heapTag.ts +13 -0
  105. package/src/diagnostics/listenOnDebugger.ts +77 -0
  106. package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
  107. package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
  108. package/src/diagnostics/logs/ansiFormat.ts +108 -0
  109. package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
  110. package/src/diagnostics/logs/diskLogger.ts +305 -0
  111. package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
  112. package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
  113. package/src/diagnostics/logs/logGitHashes.ts +30 -0
  114. package/src/diagnostics/managementPages.tsx +289 -0
  115. package/src/diagnostics/periodic.ts +89 -0
  116. package/src/diagnostics/runSaturationTest.ts +416 -0
  117. package/src/diagnostics/satSchema.ts +64 -0
  118. package/src/diagnostics/trackResources.ts +82 -0
  119. package/src/diagnostics/watchdog.ts +55 -0
  120. package/src/errors.ts +132 -0
  121. package/src/forceProduction.ts +3 -0
  122. package/src/fs.ts +72 -0
  123. package/src/heapDumps.ts +666 -0
  124. package/src/https.ts +2 -0
  125. package/src/inject.ts +1 -0
  126. package/src/library-components/ATag.tsx +84 -0
  127. package/src/library-components/Button.tsx +344 -0
  128. package/src/library-components/ButtonSelector.tsx +64 -0
  129. package/src/library-components/DropdownCustom.tsx +151 -0
  130. package/src/library-components/DropdownSelector.tsx +32 -0
  131. package/src/library-components/Input.tsx +334 -0
  132. package/src/library-components/InputLabel.tsx +198 -0
  133. package/src/library-components/InputPicker.tsx +125 -0
  134. package/src/library-components/LazyComponent.tsx +62 -0
  135. package/src/library-components/MeasureHeightCSS.tsx +48 -0
  136. package/src/library-components/MeasuredDiv.tsx +47 -0
  137. package/src/library-components/ShowMore.tsx +51 -0
  138. package/src/library-components/SyncedController.ts +171 -0
  139. package/src/library-components/TimeRangeSelector.tsx +407 -0
  140. package/src/library-components/URLParam.ts +263 -0
  141. package/src/library-components/colors.tsx +14 -0
  142. package/src/library-components/drag.ts +114 -0
  143. package/src/library-components/icons.tsx +692 -0
  144. package/src/library-components/niceStringify.ts +50 -0
  145. package/src/library-components/renderToString.ts +52 -0
  146. package/src/misc/PromiseRace.ts +101 -0
  147. package/src/misc/color.ts +30 -0
  148. package/src/misc/getParentProcessId.cs +53 -0
  149. package/src/misc/getParentProcessId.ts +53 -0
  150. package/src/misc/hash.ts +83 -0
  151. package/src/misc/ipPong.js +13 -0
  152. package/src/misc/networking.ts +2 -0
  153. package/src/misc/random.ts +45 -0
  154. package/src/misc.ts +19 -0
  155. package/src/noserverhotreload.flag +0 -0
  156. package/src/path.ts +226 -0
  157. package/src/persistentLocalStore.ts +37 -0
  158. package/src/promise.ts +15 -0
  159. package/src/server.ts +73 -0
  160. package/src/src.d.ts +1 -0
  161. package/src/test/heapProcess.ts +36 -0
  162. package/src/test/mongoSatTest.tsx +55 -0
  163. package/src/test/satTest.ts +193 -0
  164. package/src/test/test.tsx +552 -0
  165. package/src/zip.ts +92 -0
  166. package/src/zipThreaded.ts +106 -0
  167. package/src/zipThreadedWorker.js +19 -0
  168. package/tsconfig.json +27 -0
  169. package/yarnSpec.txt +56 -0
@@ -0,0 +1,753 @@
1
+ import { delay } from "socket-function/src/batching";
2
+ import { cache, lazy } from "socket-function/src/caching";
3
+ import { formatNumber } from "socket-function/src/formatting/format";
4
+ import { magenta, blue, red, green, yellow } from "socket-function/src/formatting/logColors";
5
+ import { nextId, sort, binarySearchBasic, timeInDay } from "socket-function/src/misc";
6
+ import { measureWrap } from "socket-function/src/profiling/measure";
7
+ import { canHaveChildren } from "socket-function/src/types";
8
+ import { AuthorityPath, pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
9
+ import { pathValueCommitter } from "../0-path-value-core/PathValueCommitter";
10
+ import { ReadLock, epochTime, PathValue, authorityStorage, compareTime, getNextTime } from "../0-path-value-core/pathValueCore";
11
+ import { WatchSpec, clientWatcher } from "../1-path-client/pathValueClientWatcher";
12
+ import { logNodeStats } from "../5-diagnostics/nodeMetadata";
13
+ import { getPathFromStr, getPathStr } from "../path";
14
+ import { PromiseObj } from "../promise";
15
+ import { proxyWatcher, atomicRaw, atomic } from "./PathValueProxyWatcher";
16
+ import { rawSchema } from "./pathDatabaseProxyBase";
17
+ import { getProxyPath } from "./pathValueProxy";
18
+ import { runArchiveMover } from "./archiveMoveHarness";
19
+ import { getStorageDir, getSubFolder } from "../fs";
20
+ import fs from "fs";
21
+ import { sha256 } from "js-sha256";
22
+
23
+
24
+ export interface AliveChecker<T = unknown> {
25
+ /** By accessing an object you determine the object passed to isAlive. We consider any object
26
+ * to "exist" if it has child keys (even a single key)
27
+ */
28
+ object: (wildcard: string) => T;
29
+ /** Returns a truthy value if alive, falsey if not.
30
+ * - If an proxy is returned, we check if it has the placeholder object value
31
+ * (proxies virtually always have this, as it is how Object.keys() works).
32
+ * This results in the value becoming falsey when the object is removed
33
+ * from the parent list.
34
+ * NOTE: The value might only have a single heavily nested value, not being a valid object. If the object
35
+ * lookups broken, probably just return false, to clean up the straggling values.
36
+ */
37
+ isAlive: (value: T, path: string[]) => boolean | unknown;
38
+
39
+ /** Alias for lockBasedDelete & unsafeLiveReads */
40
+ lock?: boolean;
41
+
42
+ // Delay deletions by this amount. Used for values that need to last longer than an event write,
43
+ // but not forever, such as deleting cached values after a few days.
44
+ deleteDelayTime?: number;
45
+ /** Deletes with a lock. If unsafeLiveReads is used this should be used as well.
46
+ * - MUCH slower than non-lock deletes.
47
+ * - May crash if millions of values are deleted at once.
48
+ */
49
+ lockBasedDelete?: boolean;
50
+ /** Reads live values, in a way that intermediate values may be skipped.
51
+ * NOTE: Any values GCed this way will still take the normal time for the deletion
52
+ * to be reflected on servers.
53
+ * NOTE: Any values NOT in the GC shard will be synced live anyways
54
+ * - So... maybe we should turn on unsafe live reads regardless? It's mostly dangerous
55
+ * cross shard, and our GC shards will likely line up with our PathValueServer shards,
56
+ * so, the cases we make safer are probably less likely to happen than the cases we make
57
+ * worse?
58
+ * - Although... this IS safer for undefined cleanup.
59
+ */
60
+ unsafeLiveReads?: boolean;
61
+ }
62
+ let aliveCheckers: AliveChecker<any>[] = [];
63
+
64
+ /** NOTE: Not only does alive checking only operate on flushed values, it also waits for ARCHIVE_FLUSH_LIMIT,
65
+ * (which is a lot higher). AND THEN, the changes have to be picked up by a disk audit to propagate to servers.
66
+ * AND, when nodes haven't auditted the disk, they might be in a different state. So... it should only
67
+ * be used for values users can't access anymore, and so that won't be noticed when they disappear.
68
+ */
69
+ export function registerAliveChecker<T>(config: AliveChecker<T>) {
70
+ if (config.lock) {
71
+ config.lockBasedDelete = true;
72
+ config.unsafeLiveReads = true;
73
+ }
74
+ aliveCheckers.push(config);
75
+ }
76
+
77
+ export function getAliveCheckers() {
78
+ return aliveCheckers;
79
+ }
80
+
81
+ // NOTE: Reusing the watcher (for a trivial function) was about 4X faster,
82
+ // so this extra code is definitely worth it.
83
+ let getWatchEvaluator = lazy((): {
84
+ <T>(
85
+ fnc: () => T,
86
+ config?: {
87
+ getReadLocks?: boolean;
88
+ }
89
+ ): {
90
+ pathsRead: string[];
91
+ parentPathsRead: string[];
92
+ result: T;
93
+ readLocks?: ReadLock[];
94
+ }
95
+ } => {
96
+ let nextFnc: () => unknown;
97
+ let nextGetReadLocks: boolean;
98
+ let lastResult: {
99
+ pathsRead: string[];
100
+ parentPathsRead: string[];
101
+ result: unknown;
102
+ readLocks?: ReadLock[];
103
+ } | {
104
+ error: string;
105
+ };
106
+ let watcher = proxyWatcher.createWatcher({
107
+ static: true,
108
+ runImmediately: true,
109
+ unsafeNoLocks: true,
110
+ canWrite: false,
111
+ watchFunction() {
112
+ try {
113
+ let result = nextFnc();
114
+ lastResult = {
115
+ pathsRead: Array.from(watcher.pendingWatches.paths),
116
+ parentPathsRead: Array.from(watcher.pendingWatches.parentPaths),
117
+ result
118
+ };
119
+ if (nextGetReadLocks) {
120
+ lastResult.readLocks = [];
121
+ for (let value of watcher.pendingAccesses) {
122
+ for (let v of value[1].values()) {
123
+ lastResult.readLocks.push({
124
+ path: v.path,
125
+ startTime: v.time,
126
+ // NOTE: endTime will be replaced later, so this value shouldn't matter
127
+ endTime: epochTime,
128
+ readIsTranparent: !!v.isTransparent,
129
+ });
130
+ }
131
+ }
132
+ for (let value of watcher.pendingEpochAccesses) {
133
+ for (let v of value[1].values()) {
134
+ lastResult.readLocks.push({
135
+ path: v,
136
+ startTime: epochTime,
137
+ // NOTE: endTime will be replaced later, so this value shouldn't matter
138
+ endTime: epochTime,
139
+ readIsTranparent: true,
140
+ });
141
+ }
142
+ }
143
+ }
144
+ } catch (error: any) {
145
+ lastResult = { error: error.stack || error.toString() };
146
+ }
147
+ },
148
+ });
149
+ return (fnc, config) => {
150
+ nextGetReadLocks = config?.getReadLocks || false;
151
+ nextFnc = fnc;
152
+ watcher.explicitlyTrigger();
153
+ if (!lastResult) throw new Error(`Internal error, no result?`);
154
+ if ("error" in lastResult) throw new Error(lastResult.error);
155
+ return lastResult as { pathsRead: string[]; parentPathsRead: string[]; result: any };
156
+ };
157
+ });
158
+
159
+ const wildcard = Symbol.for("wildcard");
160
+ type PathSelector = {
161
+ path: (string | typeof wildcard)[];
162
+ };
163
+
164
+ function sandboxGetPathSelector(config: {
165
+ checker: AliveChecker;
166
+ }): PathSelector {
167
+ const checker = config.checker;
168
+ const wildcardId = nextId();
169
+ let path = getPathFromStr(getProxyPath(() => checker.object(wildcardId)));
170
+ return {
171
+ path: path.map(x => x === wildcardId ? wildcard : x)
172
+ };
173
+ }
174
+
175
+
176
+ const getAllPathMatches = measureWrap(function getAllPathMatches(config: {
177
+ selectors: PathSelector[]
178
+ values: PathValue[];
179
+ }): Map<PathSelector, string[][]> {
180
+ let { selectors, values } = config;
181
+ let matches = new Map<PathSelector, string[][]>();
182
+ // NOTE: If this is slow, make a tree of prefixes, and then walk through it using
183
+ // the selector path.
184
+ for (let selector of selectors) {
185
+ let paths = new Map<string, string[]>();
186
+ for (let value of values) {
187
+ let valuePath = getPathFromStr(value.path);
188
+ //if (selector.path.length !== valuePath.length) continue;
189
+ // NOTE: We HAVE to match when valuePath.length > selector.path.length. This happens
190
+ // if an object is deleted, which is the main reason we want to use this code!
191
+ // Instead of just checking the exact path, we consider it to have matched if
192
+ // any child path has matched.
193
+ if (selector.path.length > valuePath.length) continue;
194
+ let matched = true;
195
+ for (let i = 0; i < selector.path.length; i++) {
196
+ if (selector.path[i] !== wildcard && selector.path[i] !== valuePath[i]) {
197
+ matched = false;
198
+ break;
199
+ }
200
+ }
201
+ if (matched) {
202
+ let curPath = valuePath.slice(0, selector.path.length);
203
+ let pathHash = getPathStr(curPath);
204
+ paths.set(pathHash, curPath);
205
+ }
206
+ }
207
+ if (paths) matches.set(selector, Array.from(paths.values()));
208
+ }
209
+ return matches;
210
+ });
211
+ const createRecursiveLookup = measureWrap(function createRecursiveLookup(config: {
212
+ values: PathValue[];
213
+ }): {
214
+ getValues: (path: string, parts: string[]) => PathValue[];
215
+ } {
216
+ let { values } = config;
217
+ let valuesSorted = values.slice();
218
+ sort(valuesSorted, x => x.path);
219
+ return {
220
+ getValues(path, parts) {
221
+ let index = binarySearchBasic(valuesSorted, x => x.path, path);
222
+ if (index < 0) index = ~index;
223
+ let results: PathValue[] = [];
224
+ while (index < valuesSorted.length && valuesSorted[index].path.startsWith(path)) {
225
+ results.push(valuesSorted[index]);
226
+ index++;
227
+ }
228
+ return results;
229
+ },
230
+ };
231
+ });
232
+
233
+ function sandboxedIsAlive(config: {
234
+ pathStr: string;
235
+ path: string[];
236
+ checker: AliveChecker;
237
+ authority: AuthorityPath;
238
+ unsyncedPaths: Map<AliveChecker, Map<string, {
239
+ paths: Set<string>;
240
+ parentPaths: Set<string>;
241
+ }>>;
242
+ errors: { count: number };
243
+ }): boolean {
244
+ const { pathStr, path, checker, authority, unsyncedPaths } = config;
245
+
246
+ let evalResult = getWatchEvaluator()(() => {
247
+ let proxy = rawSchema()() as any;
248
+ for (let part of path) {
249
+ if (!canHaveChildren(proxy)) {
250
+ proxy = undefined;
251
+ break;
252
+ }
253
+ proxy = proxy[part];
254
+ }
255
+ try {
256
+ return !!atomicRaw(checker.isAlive(proxy, path));
257
+ } catch {
258
+ config.errors.count++;
259
+ return true;
260
+ }
261
+ });
262
+
263
+ let obj: {
264
+ paths: Set<string>;
265
+ parentPaths: Set<string>;
266
+ } | undefined;
267
+ function getObj() {
268
+ if (obj) return obj;
269
+ let checkerObj = unsyncedPaths.get(checker);
270
+ if (!checkerObj) {
271
+ checkerObj = new Map();
272
+ unsyncedPaths.set(checker, checkerObj);
273
+ }
274
+ obj = checkerObj.get(pathStr);
275
+ if (obj) {
276
+ return obj;
277
+ }
278
+ obj = {
279
+ paths: new Set(),
280
+ parentPaths: new Set(),
281
+ };
282
+ checkerObj.set(pathStr, obj);
283
+ return obj;
284
+ }
285
+
286
+ for (let path of evalResult.pathsRead) {
287
+ if (authorityStorage.isSynced(path)) continue;
288
+ if (!pathValueAuthority2.isInAuthority(authority, path)) {
289
+ getObj().paths.add(path);
290
+ }
291
+ }
292
+ for (let path of evalResult.parentPathsRead) {
293
+ if (authorityStorage.isParentSynced(path)) continue;
294
+ if (!pathValueAuthority2.isInAuthority(authority, path)) {
295
+ getObj().parentPaths.add(path);
296
+ }
297
+ }
298
+
299
+ return evalResult.result;
300
+ }
301
+ function sandboxedGetLocks(config: {
302
+ pathStr: string;
303
+ path: string[];
304
+ checker: AliveChecker;
305
+ }): ReadLock[] {
306
+ const { pathStr, path, checker } = config;
307
+
308
+ let evalResult = getWatchEvaluator()(() => {
309
+ let value = proxyWatcher.getCallbackPathValue(pathStr);
310
+ return !!atomic(checker.isAlive(value, path));
311
+ }, { getReadLocks: true });
312
+
313
+ return evalResult.readLocks!;
314
+ }
315
+
316
+ type ValueFree = {
317
+ value: PathValue;
318
+ locks: ReadLock[] | undefined;
319
+ };
320
+
321
+ // Write wrapper that manages delay times, and calls all functions
322
+ const runAliveCheckerIterationBase = measureWrap(function runAliveCheckerIterationBase(config: {
323
+ checkPaths: Map<PathSelector, string[][]>;
324
+ selectors: Map<PathSelector, AliveChecker>;
325
+ useLocks: boolean;
326
+ authority: AuthorityPath;
327
+ getValues: (path: string, parts: string[]) => PathValue[];
328
+ unsyncedPaths: Map<AliveChecker, Map<string, {
329
+ paths: Set<string>;
330
+ parentPaths: Set<string>;
331
+ }>>;
332
+ delayedGCRequests: Map<string, {
333
+ path: string[];
334
+ freeTime: number;
335
+ }>,
336
+ }): ValueFree[][] {
337
+ const now = Date.now();
338
+ let { checkPaths, selectors, authority, getValues, unsyncedPaths, delayedGCRequests } = config;
339
+
340
+ let flatFrees: ValueFree[] = [];
341
+ let allFrees: ValueFree[][] = [flatFrees];
342
+
343
+ let checks = 0;
344
+ let frees = 0;
345
+ let allDelayedFrees = 0;
346
+ let allPreviousDelayedFrees = 0;
347
+ let errored = { count: 0 };
348
+
349
+ let usedGCPaths = new Set<string>();
350
+
351
+ console.log(`Checking ${formatNumber(checkPaths.size)} selectors`);
352
+ for (let [selector, paths] of checkPaths) {
353
+ let selectorFrees = 0;
354
+ let delayedFrees = 0;
355
+ let previousDelayedFrees = 0;
356
+ console.log(` ${selector.path.map(x => x === wildcard ? magenta("*") : blue(String(x))).join(".")}`);
357
+ for (let path of paths) {
358
+ checks++;
359
+ let pathStr = getPathStr(path);
360
+ let checker = selectors.get(selector)!;
361
+ let isAlive = sandboxedIsAlive({ path, pathStr, checker, authority, unsyncedPaths, errors: errored });
362
+ if (isAlive) {
363
+ if (checker.deleteDelayTime) {
364
+ delayedGCRequests.delete(pathStr);
365
+ }
366
+ continue;
367
+ }
368
+
369
+ if (checker.deleteDelayTime) {
370
+ // NOTE: Checkers must have a constant PathSelector, so we don't have to worry about
371
+ // delayed requests that are matched once, but not the second time. We also
372
+ // only check it when checking the selector, not on a timer, so that no matter
373
+ // the delay (unless it is very short), we check it against real data twice. This decreases
374
+ // false positives? Or something...
375
+ let request = delayedGCRequests.get(pathStr);
376
+ let freeTime = now + checker.deleteDelayTime;
377
+ let existedBefore = !!request;
378
+ if (!request) {
379
+ request = { path, freeTime };
380
+ delayedGCRequests.set(pathStr, request);
381
+ usedGCPaths.add(pathStr);
382
+ }
383
+ if (freeTime < request.freeTime) {
384
+ request.freeTime = freeTime;
385
+ }
386
+
387
+ if (request.freeTime > now) {
388
+ delayedFrees++;
389
+ allDelayedFrees++;
390
+ if (existedBefore) {
391
+ previousDelayedFrees++;
392
+ allPreviousDelayedFrees++;
393
+ }
394
+ continue;
395
+ }
396
+ delayedGCRequests.delete(pathStr);
397
+ }
398
+
399
+ frees++;
400
+ selectorFrees++;
401
+
402
+ // Get recursive paths
403
+ let recursiveValues = getValues(pathStr, path);
404
+ if (recursiveValues.length === 0) {
405
+ console.warn(`No values found for path, which matched selector. How did it match a selector, and not exist? Path: ${pathStr}`);
406
+ continue;
407
+ }
408
+ if (!checker.lockBasedDelete) {
409
+ for (let value of recursiveValues) {
410
+ flatFrees.push({ value, locks: undefined });
411
+ }
412
+ } else {
413
+ let locks = sandboxedGetLocks({ path, pathStr, checker });
414
+ let curFrees: ValueFree[] = [];
415
+ for (let value of recursiveValues) {
416
+ curFrees.push({ value, locks });
417
+ }
418
+ allFrees.push(curFrees);
419
+ }
420
+ }
421
+ console.log(` Dead objects ${formatNumber(selectorFrees)} / ${formatNumber(paths.length)}`);
422
+ if (delayedFrees) {
423
+ console.log(` Future dead objects: ${formatNumber(delayedFrees)} (${formatNumber(previousDelayedFrees)} previously delayed)`);
424
+ }
425
+ }
426
+
427
+ console.log(`Dead objects: ${formatNumber(frees)} / ${formatNumber(checks)}`);
428
+ console.log(`Future dead objects: ${formatNumber(allDelayedFrees)} / ${formatNumber(checks)} (${formatNumber(allPreviousDelayedFrees)} previously delayed)`);
429
+ if (errored.count) {
430
+ console.warn(red(`Errored out checks: ${formatNumber(errored.count)}`));
431
+ }
432
+
433
+ return allFrees.filter(x => x.length > 0);
434
+ });
435
+
436
+ let delayedGCRequestsLists = new Map<string, Map<string, {
437
+ path: string[];
438
+ freeTime: number;
439
+ }>>();
440
+ let loadDelayedGCRequests = cache(async (hash: string) => {
441
+ let cacheGCPath = getStorageDir() + `${hash}_gcRequests.txt`;
442
+ if (!fs.existsSync(cacheGCPath)) return;
443
+ try {
444
+ let contents = await fs.promises.readFile(cacheGCPath, "utf8");
445
+ let results = JSON.parse(contents) as [string, { path: string[], freeTime: number }][];
446
+ delayedGCRequestsLists.set(hash, new Map(results));
447
+ console.log(`Loaded ${results.length} GC requests for ${hash}`);
448
+ } catch (e) {
449
+ console.warn(`Failed to load GC requests for ${hash}`, e);
450
+ }
451
+ });
452
+ async function saveGCRequests(hash: string) {
453
+ let cacheGCPath = getStorageDir() + `${hash}_gcRequests.txt`;
454
+ let serialized = JSON.stringify(Array.from(delayedGCRequestsLists.get(hash) || new Map()));
455
+ await fs.promises.writeFile(cacheGCPath, serialized);
456
+ }
457
+
458
+ // ALWAYS run a non-lock gc, to remove undefined values, even if no AliveCheckers use it.
459
+ // Lock frees just create undefines, which will eventually trickly down to a
460
+ // a gc of the undefined value.
461
+ export async function runAliveCheckerIteration(config?: {
462
+ // Force, ignoring how much we reduce, merging EVEN if we don't have any frees
463
+ force?: boolean;
464
+ }) {
465
+ let outOfShardValues = new Map<string, PathValue>();
466
+ let pendingWatches: undefined | {
467
+ paths: Set<string>;
468
+ parentPaths: Set<string>;
469
+ promiseObj: PromiseObj<void>;
470
+ };
471
+ let watchSpec: WatchSpec = {
472
+ debugName: "gcOutOfShardWatches",
473
+ callback: (data) => {
474
+ for (let value of data.pathSources || []) {
475
+ outOfShardValues.set(value.path, value);
476
+ }
477
+
478
+ if (pendingWatches) {
479
+ for (let path of Array.from(pendingWatches.paths)) {
480
+ let value = authorityStorage.getValueOrEpochIfSynced(path);
481
+ if (value) {
482
+ pendingWatches.paths.delete(path);
483
+ outOfShardValues.set(path, value);
484
+ }
485
+ }
486
+ for (let path of Array.from(pendingWatches.parentPaths)) {
487
+ if (authorityStorage.isParentSynced(path)) {
488
+ pendingWatches.parentPaths.delete(path);
489
+ let childPaths = authorityStorage.getPathsFromParent(path);
490
+ for (let path of childPaths || []) {
491
+ let value = authorityStorage.getValueOrEpochIfSynced(path);
492
+ if (value) {
493
+ outOfShardValues.set(path, value);
494
+ }
495
+ }
496
+ }
497
+ }
498
+
499
+ if (pendingWatches.paths.size === 0 && pendingWatches.parentPaths.size === 0) {
500
+ console.log(`All paths synced, rerunning`);
501
+ pendingWatches.promiseObj.resolve();
502
+ pendingWatches = undefined;
503
+ } else {
504
+ console.log(`Waiting on paths: ${formatNumber(pendingWatches.paths.size)}, parent parents: ${formatNumber(pendingWatches.parentPaths.size)}`);
505
+ }
506
+ }
507
+ },
508
+ paths: new Set(),
509
+ parentPaths: new Set(),
510
+ };
511
+
512
+ try {
513
+ let checkers = getAliveCheckers();
514
+
515
+ function checkerHash(checker: AliveChecker) {
516
+ return JSON.stringify({ locks: !!checker.lockBasedDelete, live: !!checker.unsafeLiveReads });
517
+ }
518
+ let checkerGroups = new Map<string, AliveChecker[]>();
519
+ for (let checker of checkers) {
520
+ let hash = checkerHash(checker);
521
+ let group = checkerGroups.get(hash);
522
+ if (!group) {
523
+ checkerGroups.set(hash, group = []);
524
+ }
525
+ group.push(checker);
526
+ }
527
+ for (let [hash, checkers] of checkerGroups) {
528
+ hash = sha256(hash);
529
+ let example = checkers[0];
530
+ // gclive
531
+ let type = `gc${example.unsafeLiveReads ? "live" : ""}${example.lockBasedDelete ? "INVALIDUPDATE" : ""}`;
532
+
533
+ console.log(`Starting group ${hash} (${type})`);
534
+
535
+ await loadDelayedGCRequests(hash);
536
+ let gcRequestsBase = delayedGCRequestsLists.get(hash);
537
+ if (!gcRequestsBase) {
538
+ gcRequestsBase = new Map();
539
+ delayedGCRequestsLists.set(hash, gcRequestsBase);
540
+ }
541
+ let gcRequests = gcRequestsBase;
542
+
543
+ let lockWrites: PathValue[] = [];
544
+ await runArchiveMover({
545
+ outputType: type,
546
+ readLiveData: example.unsafeLiveReads,
547
+ force: config?.force,
548
+ async runMover(config) {
549
+ // Filtering out duplicates and undefineds make the rest of the code simpler. It's also
550
+ // very fast, so we might as well do it, even if we are using locks (which mean our filtered
551
+ // undefined won't result in any deletions).
552
+ let values = [
553
+ ...config.values,
554
+ ...Array.from(outOfShardValues.values()),
555
+ ];
556
+ let latestValues = new Map<string, PathValue>();
557
+ for (let value of values) {
558
+ let prev = latestValues.get(value.path);
559
+ if (prev && compareTime(value.time, prev.time) < 0) continue;
560
+ latestValues.set(value.path, value);
561
+ }
562
+ values = Array.from(latestValues.values());
563
+ values = values.filter(x => !x.canGCValue);
564
+
565
+ let recursiveLookup = createRecursiveLookup({ values });
566
+
567
+ let selectors = new Map<PathSelector, AliveChecker>();
568
+ let checkerToSelector = new Map<AliveChecker, PathSelector>();
569
+
570
+ for (let checker of checkers) {
571
+ let selector = sandboxGetPathSelector({ checker });
572
+ selectors.set(selector, checker);
573
+ checkerToSelector.set(checker, selector);
574
+ }
575
+
576
+ let remainingPaths = getAllPathMatches({ values, selectors: Array.from(selectors.keys()) });
577
+
578
+ let freeLists: ValueFree[][][] = [];
579
+ let loops = 0;
580
+
581
+ while (true) {
582
+ loops++;
583
+ let unsyncedPaths = new Map<AliveChecker, Map<string, {
584
+ paths: Set<string>;
585
+ parentPaths: Set<string>;
586
+ }>>();
587
+
588
+ let frees = authorityStorage.temporaryOverride(values, () =>
589
+ runAliveCheckerIterationBase({
590
+ checkPaths: remainingPaths,
591
+ selectors,
592
+ useLocks: !!example.lockBasedDelete,
593
+ authority: config.authority,
594
+ getValues: recursiveLookup.getValues,
595
+ unsyncedPaths,
596
+ delayedGCRequests: gcRequests,
597
+ })
598
+ );
599
+
600
+ freeLists.push(frees);
601
+ if (unsyncedPaths.size === 0) break;
602
+
603
+ remainingPaths = new Map();
604
+ for (let [checker, paths] of unsyncedPaths) {
605
+ let selector = checkerToSelector.get(checker)!;
606
+ remainingPaths.set(selector, Array.from(paths.keys()).map(x => getPathFromStr(x)));
607
+ }
608
+
609
+ let checkCount = 0;
610
+ let paths = new Set<string>();
611
+ let parentPaths = new Set<string>();
612
+ for (let map of unsyncedPaths.values()) {
613
+ for (let obj of map.values()) {
614
+ checkCount++;
615
+ for (let path of obj.paths) {
616
+ paths.add(path);
617
+ }
618
+ for (let path of obj.parentPaths) {
619
+ parentPaths.add(path);
620
+ }
621
+ }
622
+ }
623
+
624
+ for (let path of paths) {
625
+ watchSpec.paths.add(path);
626
+ }
627
+ for (let path of parentPaths) {
628
+ watchSpec.parentPaths.add(path);
629
+ }
630
+
631
+ console.log(`Out of shard unsynced accesses (loop ${loops})`);
632
+ console.log(` currently unsynced checkers ${formatNumber(unsyncedPaths.size)}`);
633
+ console.log(` currently unsynced objects ${formatNumber(checkCount)}`);
634
+ console.log(` currently unsynced paths ${formatNumber(paths.size)}, unsynced parent paths ${formatNumber(parentPaths.size)}`);
635
+ console.log(` Total path watches ${formatNumber(watchSpec.paths.size)}, total parent path watches ${formatNumber(watchSpec.parentPaths.size)}`);
636
+ console.log(` Total out of shard values ${formatNumber(outOfShardValues.size)}`);
637
+ logNodeStats("archives|Unsynced Checkers", formatNumber)(paths.size);
638
+ logNodeStats("archives|Unsynced Paths", formatNumber)(unsyncedPaths.size);
639
+ logNodeStats("archives|Unsynced Parent Paths", formatNumber)(parentPaths.size);
640
+
641
+ clientWatcher.setWatches(watchSpec);
642
+
643
+ let wait = new PromiseObj();
644
+ pendingWatches = {
645
+ paths,
646
+ parentPaths,
647
+ promiseObj: wait,
648
+ };
649
+ await Promise.race([delay(15000), wait.promise]);
650
+ }
651
+ {
652
+ let totalFreeCount = 0;
653
+ for (let frees of freeLists) {
654
+ for (let freeList of frees) {
655
+ totalFreeCount += freeList.length;
656
+ }
657
+ }
658
+ console.log(green(`Free now: ${formatNumber(totalFreeCount)} / ${formatNumber(values.length)}`));
659
+ logNodeStats("archives|Free now", formatNumber)(totalFreeCount);
660
+ }
661
+ {
662
+ let pendingFrees = gcRequests.size;
663
+ console.log(yellow(`Stored future frees: ${formatNumber(pendingFrees)} / ${formatNumber(values.length)}`));
664
+
665
+ let futureByDayCount = new Map<number, number>();
666
+ let now = Date.now();
667
+ for (let obj of gcRequests.values()) {
668
+ let day = Math.floor((obj.freeTime - now) / timeInDay);
669
+ let value = futureByDayCount.get(day) || 0;
670
+ value++;
671
+ futureByDayCount.set(day, value);
672
+ }
673
+ let dayCounts = Array.from(futureByDayCount);
674
+ sort(dayCounts, x => x[0]);
675
+ for (let [day, count] of dayCounts) {
676
+ console.log(` < ${day} days: ${formatNumber(count)}`);
677
+ }
678
+
679
+ logNodeStats("archives|Stored future frees", formatNumber)(pendingFrees);
680
+ }
681
+
682
+ if (example.lockBasedDelete) {
683
+ let lockCount = 0;
684
+ for (let frees of freeLists) {
685
+ for (let freeList of frees) {
686
+ // We need to extend our value write time as later as possible, otherwise
687
+ // it will be rejected (it needs to be set almost immediately before we commit it).
688
+ // This is still a BIT dangerous, because if we have 1 million values we might
689
+ // not iterate in under 30 seconds, but... it's good enough for now.
690
+ // (Also, at least some will be accepted, so we will make progress).
691
+ let time = getNextTime();
692
+ let locks = freeList[0].locks!;
693
+ for (let lock of locks) {
694
+ lock.endTime = time;
695
+ lockCount++;
696
+ }
697
+ for (let free of freeList) {
698
+ lockWrites.push({
699
+ path: free.value.path,
700
+ time,
701
+
702
+ locks,
703
+ value: undefined,
704
+ isValueLazy: false,
705
+
706
+ canGCValue: true,
707
+ event: undefined,
708
+ isTransparent: true,
709
+ valid: true,
710
+ source: undefined,
711
+ lockCount: locks.length,
712
+ updateCount: 0,
713
+ });
714
+ }
715
+ }
716
+ }
717
+ console.log(`Committing with ${formatNumber(lockCount)} locks`);
718
+ return { newValues: "abort" };
719
+ }
720
+
721
+ let allFrees = new Set(freeLists.flat().flat().map(x => x.value));
722
+ values = values.filter(x => !allFrees.has(x));
723
+
724
+ // NOTE: We don't notify any servers about this. We will eventually have to write
725
+ // audit code to periodically check against the disk to get updates.
726
+ return {
727
+ newValues: {
728
+ "": values,
729
+ },
730
+ };
731
+ },
732
+ });
733
+
734
+ await saveGCRequests(hash);
735
+
736
+ if (lockWrites.length > 0) {
737
+ // We are just going to hope committing 100K raw writes doesn't break anything. If it does...
738
+ // I'm sorry. But then again, "lock"-type gc is probably overkill anyways. How much user-accessible
739
+ // data do you have that you need to free it? GCing is general for unreachable data, created
740
+ // temporarily to communicate between servers, such as temporary images, or... to delete
741
+ // lookup deleted keys, which shouldn't need locks anyways.
742
+ pathValueCommitter.commitValues(lockWrites, undefined);
743
+ await pathValueCommitter.waitForValuesToCommit();
744
+ console.log(`Committed ${formatNumber(lockWrites.length)} writes`);
745
+ }
746
+ }
747
+ } finally {
748
+ watchSpec.paths.clear();
749
+ watchSpec.parentPaths.clear();
750
+ outOfShardValues.clear();
751
+ clientWatcher.setWatches(watchSpec);
752
+ }
753
+ }