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.
- package/.dependency-cruiser.js +304 -0
- package/.eslintrc.js +51 -0
- package/.github/copilot-instructions.md +1 -0
- package/.vscode/settings.json +25 -0
- package/bin/deploy.js +4 -0
- package/bin/function.js +4 -0
- package/bin/server.js +4 -0
- package/costsBenefits.txt +112 -0
- package/deploy.ts +3 -0
- package/inject.ts +1 -0
- package/package.json +60 -0
- package/prompts.txt +54 -0
- package/spec.txt +820 -0
- package/src/-a-archives/archiveCache.ts +913 -0
- package/src/-a-archives/archives.ts +148 -0
- package/src/-a-archives/archivesBackBlaze.ts +792 -0
- package/src/-a-archives/archivesDisk.ts +418 -0
- package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
- package/src/-a-auth/certs.ts +517 -0
- package/src/-a-auth/der.ts +122 -0
- package/src/-a-auth/ed25519.ts +1015 -0
- package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
- package/src/-b-authorities/dnsAuthority.ts +203 -0
- package/src/-b-authorities/emailAuthority.ts +57 -0
- package/src/-c-identity/IdentityController.ts +200 -0
- package/src/-d-trust/NetworkTrust2.ts +150 -0
- package/src/-e-certs/EdgeCertController.ts +288 -0
- package/src/-e-certs/certAuthority.ts +192 -0
- package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
- package/src/-g-core-values/NodeCapabilities.ts +134 -0
- package/src/-g-core-values/oneTimeForward.ts +91 -0
- package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
- package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
- package/src/0-path-value-core/LoggingClient.tsx +24 -0
- package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
- package/src/0-path-value-core/PathController.ts +1 -0
- package/src/0-path-value-core/PathValueCommitter.ts +565 -0
- package/src/0-path-value-core/PathValueController.ts +231 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
- package/src/0-path-value-core/debugLogs.ts +90 -0
- package/src/0-path-value-core/pathValueArchives.ts +483 -0
- package/src/0-path-value-core/pathValueCore.ts +2217 -0
- package/src/1-path-client/RemoteWatcher.ts +558 -0
- package/src/1-path-client/pathValueClientWatcher.ts +702 -0
- package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
- package/src/2-proxy/archiveMoveHarness.ts +376 -0
- package/src/2-proxy/garbageCollection.ts +753 -0
- package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
- package/src/2-proxy/pathValueProxy.ts +139 -0
- package/src/2-proxy/schema2.ts +518 -0
- package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
- package/src/3-path-functions/PathFunctionRunner.ts +619 -0
- package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
- package/src/3-path-functions/deployBlock.ts +10 -0
- package/src/3-path-functions/deployCheck.ts +7 -0
- package/src/3-path-functions/deployMain.ts +160 -0
- package/src/3-path-functions/pathFunctionLoader.ts +282 -0
- package/src/3-path-functions/syncSchema.ts +475 -0
- package/src/3-path-functions/tests/functionsTest.ts +135 -0
- package/src/3-path-functions/tests/rejectTest.ts +77 -0
- package/src/4-dom/css.tsx +29 -0
- package/src/4-dom/cssTypes.d.ts +212 -0
- package/src/4-dom/qreact.tsx +2322 -0
- package/src/4-dom/qreactTest.tsx +417 -0
- package/src/4-querysub/Querysub.ts +877 -0
- package/src/4-querysub/QuerysubController.ts +620 -0
- package/src/4-querysub/copyEvent.ts +0 -0
- package/src/4-querysub/permissions.ts +289 -0
- package/src/4-querysub/permissionsShared.ts +1 -0
- package/src/4-querysub/querysubPrediction.ts +525 -0
- package/src/5-diagnostics/FullscreenModal.tsx +67 -0
- package/src/5-diagnostics/GenericFormat.tsx +165 -0
- package/src/5-diagnostics/Modal.tsx +79 -0
- package/src/5-diagnostics/Table.tsx +183 -0
- package/src/5-diagnostics/TimeGrouper.tsx +114 -0
- package/src/5-diagnostics/diskValueAudit.ts +216 -0
- package/src/5-diagnostics/memoryValueAudit.ts +442 -0
- package/src/5-diagnostics/nodeMetadata.ts +135 -0
- package/src/5-diagnostics/qreactDebug.tsx +309 -0
- package/src/5-diagnostics/shared.ts +26 -0
- package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
- package/src/TestController.ts +35 -0
- package/src/allowclient.flag +0 -0
- package/src/bits.ts +86 -0
- package/src/buffers.ts +69 -0
- package/src/config.ts +53 -0
- package/src/config2.ts +48 -0
- package/src/diagnostics/ActionsHistory.ts +56 -0
- package/src/diagnostics/NodeViewer.tsx +503 -0
- package/src/diagnostics/SizeLimiter.ts +62 -0
- package/src/diagnostics/TimeDebug.tsx +18 -0
- package/src/diagnostics/benchmark.ts +139 -0
- package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
- package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
- package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
- package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
- package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
- package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
- package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
- package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
- package/src/diagnostics/heapTag.ts +13 -0
- package/src/diagnostics/listenOnDebugger.ts +77 -0
- package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
- package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
- package/src/diagnostics/logs/ansiFormat.ts +108 -0
- package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
- package/src/diagnostics/logs/diskLogger.ts +305 -0
- package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
- package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
- package/src/diagnostics/logs/logGitHashes.ts +30 -0
- package/src/diagnostics/managementPages.tsx +289 -0
- package/src/diagnostics/periodic.ts +89 -0
- package/src/diagnostics/runSaturationTest.ts +416 -0
- package/src/diagnostics/satSchema.ts +64 -0
- package/src/diagnostics/trackResources.ts +82 -0
- package/src/diagnostics/watchdog.ts +55 -0
- package/src/errors.ts +132 -0
- package/src/forceProduction.ts +3 -0
- package/src/fs.ts +72 -0
- package/src/heapDumps.ts +666 -0
- package/src/https.ts +2 -0
- package/src/inject.ts +1 -0
- package/src/library-components/ATag.tsx +84 -0
- package/src/library-components/Button.tsx +344 -0
- package/src/library-components/ButtonSelector.tsx +64 -0
- package/src/library-components/DropdownCustom.tsx +151 -0
- package/src/library-components/DropdownSelector.tsx +32 -0
- package/src/library-components/Input.tsx +334 -0
- package/src/library-components/InputLabel.tsx +198 -0
- package/src/library-components/InputPicker.tsx +125 -0
- package/src/library-components/LazyComponent.tsx +62 -0
- package/src/library-components/MeasureHeightCSS.tsx +48 -0
- package/src/library-components/MeasuredDiv.tsx +47 -0
- package/src/library-components/ShowMore.tsx +51 -0
- package/src/library-components/SyncedController.ts +171 -0
- package/src/library-components/TimeRangeSelector.tsx +407 -0
- package/src/library-components/URLParam.ts +263 -0
- package/src/library-components/colors.tsx +14 -0
- package/src/library-components/drag.ts +114 -0
- package/src/library-components/icons.tsx +692 -0
- package/src/library-components/niceStringify.ts +50 -0
- package/src/library-components/renderToString.ts +52 -0
- package/src/misc/PromiseRace.ts +101 -0
- package/src/misc/color.ts +30 -0
- package/src/misc/getParentProcessId.cs +53 -0
- package/src/misc/getParentProcessId.ts +53 -0
- package/src/misc/hash.ts +83 -0
- package/src/misc/ipPong.js +13 -0
- package/src/misc/networking.ts +2 -0
- package/src/misc/random.ts +45 -0
- package/src/misc.ts +19 -0
- package/src/noserverhotreload.flag +0 -0
- package/src/path.ts +226 -0
- package/src/persistentLocalStore.ts +37 -0
- package/src/promise.ts +15 -0
- package/src/server.ts +73 -0
- package/src/src.d.ts +1 -0
- package/src/test/heapProcess.ts +36 -0
- package/src/test/mongoSatTest.tsx +55 -0
- package/src/test/satTest.ts +193 -0
- package/src/test/test.tsx +552 -0
- package/src/zip.ts +92 -0
- package/src/zipThreaded.ts +106 -0
- package/src/zipThreadedWorker.js +19 -0
- package/tsconfig.json +27 -0
- 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
|
+
}
|