querysub 0.403.0 → 0.404.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/.cursorrules +2 -0
- package/bin/audit-imports.js +4 -0
- package/package.json +7 -4
- package/spec.txt +77 -0
- package/src/-a-archives/archiveCache.ts +9 -4
- package/src/-a-archives/archivesBackBlaze.ts +1039 -1039
- package/src/-a-auth/certs.ts +0 -12
- package/src/-c-identity/IdentityController.ts +12 -3
- package/src/-f-node-discovery/NodeDiscovery.ts +32 -26
- package/src/-g-core-values/NodeCapabilities.ts +12 -2
- package/src/0-path-value-core/AuthorityLookup.ts +239 -0
- package/src/0-path-value-core/LockWatcher2.ts +150 -0
- package/src/0-path-value-core/PathRouter.ts +535 -0
- package/src/0-path-value-core/PathRouterRouteOverride.ts +72 -0
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +65 -0
- package/src/0-path-value-core/PathValueCommitter.ts +222 -488
- package/src/0-path-value-core/PathValueController.ts +277 -239
- package/src/0-path-value-core/PathWatcher.ts +534 -0
- package/src/0-path-value-core/ShardPrefixes.ts +31 -0
- package/src/0-path-value-core/ValidStateComputer.ts +303 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +1 -1
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +80 -44
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +13 -16
- package/src/0-path-value-core/auditLogs.ts +2 -0
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +97 -0
- package/src/0-path-value-core/pathValueArchives.ts +490 -492
- package/src/0-path-value-core/pathValueCore.ts +195 -1496
- package/src/0-path-value-core/startupAuthority.ts +74 -0
- package/src/1-path-client/RemoteWatcher.ts +90 -82
- package/src/1-path-client/pathValueClientWatcher.ts +808 -815
- package/src/2-proxy/PathValueProxyWatcher.ts +10 -8
- package/src/2-proxy/archiveMoveHarness.ts +182 -214
- package/src/2-proxy/garbageCollection.ts +9 -8
- package/src/2-proxy/schema2.ts +21 -1
- package/src/3-path-functions/PathFunctionHelpers.ts +206 -180
- package/src/3-path-functions/PathFunctionRunner.ts +943 -766
- package/src/3-path-functions/PathFunctionRunnerMain.ts +5 -3
- package/src/3-path-functions/pathFunctionLoader.ts +2 -2
- package/src/3-path-functions/syncSchema.ts +592 -521
- package/src/4-deploy/deployFunctions.ts +19 -4
- package/src/4-deploy/deployGetFunctionsInner.ts +8 -2
- package/src/4-deploy/deployMain.ts +51 -68
- package/src/4-deploy/edgeClientWatcher.tsx +1 -1
- package/src/4-deploy/edgeNodes.ts +2 -2
- package/src/4-dom/qreact.tsx +2 -4
- package/src/4-dom/qreactTest.tsx +7 -13
- package/src/4-querysub/Querysub.ts +21 -8
- package/src/4-querysub/QuerysubController.ts +45 -29
- package/src/4-querysub/permissions.ts +2 -2
- package/src/4-querysub/querysubPrediction.ts +80 -70
- package/src/4-querysub/schemaHelpers.ts +5 -1
- package/src/5-diagnostics/GenericFormat.tsx +14 -9
- package/src/archiveapps/archiveGCEntry.tsx +9 -2
- package/src/archiveapps/archiveJoinEntry.ts +87 -84
- package/src/archiveapps/archiveMergeEntry.tsx +2 -0
- package/src/bits.ts +19 -0
- package/src/config.ts +21 -3
- package/src/config2.ts +23 -48
- package/src/deployManager/components/DeployPage.tsx +7 -3
- package/src/deployManager/machineSchema.ts +4 -1
- package/src/diagnostics/ActionsHistory.ts +3 -8
- package/src/diagnostics/AuditLogPage.tsx +2 -3
- package/src/diagnostics/FunctionCallInfo.tsx +141 -0
- package/src/diagnostics/FunctionCallInfoState.ts +162 -0
- package/src/diagnostics/MachineThreadInfo.tsx +1 -1
- package/src/diagnostics/NodeViewer.tsx +37 -48
- package/src/diagnostics/SyncTestPage.tsx +241 -0
- package/src/diagnostics/auditImportViolations.ts +185 -0
- package/src/diagnostics/listenOnDebugger.ts +3 -3
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +10 -4
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +2 -2
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +24 -22
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +1 -1
- package/src/diagnostics/logs/diskLogGlobalContext.ts +1 -0
- package/src/diagnostics/logs/errorNotifications2/logWatcher.ts +1 -3
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +34 -16
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +4 -6
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleInstanceTableView.tsx +36 -5
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +19 -5
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +15 -7
- package/src/diagnostics/logs/lifeCycleAnalysis/NestedLifeCycleInfo.tsx +28 -106
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMatching.ts +2 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMisc.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +18 -7
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +3 -0
- package/src/diagnostics/managementPages.tsx +10 -3
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +20 -26
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +6 -4
- package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +2 -2
- package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +7 -9
- package/src/diagnostics/misc-pages/SnapshotViewer.tsx +23 -12
- package/src/diagnostics/misc-pages/archiveViewerShared.tsx +1 -1
- package/src/diagnostics/pathAuditer.ts +486 -0
- package/src/diagnostics/pathAuditerCallback.ts +20 -0
- package/src/diagnostics/watchdog.ts +8 -1
- package/src/library-components/URLParam.ts +1 -1
- package/src/misc/hash.ts +1 -0
- package/src/path.ts +21 -7
- package/src/server.ts +54 -47
- package/src/user-implementation/loginEmail.tsx +1 -1
- package/tempnotes.txt +67 -0
- package/test.ts +288 -95
- package/src/0-path-value-core/NodePathAuthorities.ts +0 -1057
- package/src/0-path-value-core/PathController.ts +0 -1
- package/src/5-diagnostics/diskValueAudit.ts +0 -218
- package/src/5-diagnostics/memoryValueAudit.ts +0 -438
- package/src/archiveapps/lockTest.ts +0 -127
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
import { cacheJSONArgsEqual,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { CallSpec, FunctionResult, FunctionSpec, debugCallSpec, functionSchema, overrideCurrentCall } from "../3-path-functions/PathFunctionRunner";
|
|
1
|
+
import { cacheJSONArgsEqual, lazy } from "socket-function/src/caching";
|
|
2
|
+
import { MAX_CHANGE_AGE, PathValue, ReadLock, Time, authorityStorage, predictionLockVersion } from "../0-path-value-core/pathValueCore";
|
|
3
|
+
import { validStateComputer } from "../0-path-value-core/ValidStateComputer";
|
|
4
|
+
import { proxyWatcher, atomicObjectRead, DryRunResult, getCurrentCallCreationProxy } from "../2-proxy/PathValueProxyWatcher";
|
|
5
|
+
import { getProxyPath } from "../2-proxy/pathValueProxy";
|
|
6
|
+
import { CallSpec, FunctionResult, FunctionSpec, functionSchema, overrideCurrentCall } from "../3-path-functions/PathFunctionRunner";
|
|
8
7
|
import { getModuleFromConfig, setGitURLMapping } from "../3-path-functions/pathFunctionLoader";
|
|
9
8
|
import { logErrors } from "../errors";
|
|
10
|
-
import {
|
|
11
|
-
import { Querysub, QuerysubController, QuerysubControllerBase,
|
|
9
|
+
import { getPathFromStr, getPathStr1 } from "../path";
|
|
10
|
+
import { Querysub, QuerysubController, QuerysubControllerBase, registerPredictionBlocker, querysubNodeId } from "./QuerysubController";
|
|
12
11
|
import { Benchmark } from "../diagnostics/benchmark";
|
|
13
12
|
import { parseArgs } from "../3-path-functions/PathFunctionHelpers";
|
|
14
|
-
import {
|
|
15
|
-
import { green, magenta, red, yellow } from "socket-function/src/formatting/logColors";
|
|
13
|
+
import { magenta, red } from "socket-function/src/formatting/logColors";
|
|
16
14
|
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
17
15
|
|
|
18
16
|
import { setFlag } from "socket-function/require/compileFlags";
|
|
19
17
|
import cbor from "cbor-x";
|
|
20
18
|
import { FunctionMetadata } from "../3-path-functions/syncSchema";
|
|
21
|
-
import { PromiseObj, isNode
|
|
22
|
-
import {
|
|
23
|
-
import { isLocal } from "../config";
|
|
24
|
-
import { onAllPredictionsFinished } from "../-0-hooks/hooks";
|
|
25
|
-
import { t } from "../2-proxy/schema2";
|
|
19
|
+
import { PromiseObj, isNode } from "socket-function/src/misc";
|
|
20
|
+
import { isPublic } from "../config";
|
|
26
21
|
import { clientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
27
22
|
import { cacheAsyncLimitedJSON } from "../functional/promiseCache";
|
|
28
23
|
import { addEpsilons } from "../bits";
|
|
29
24
|
setFlag(require, "cbor-x", "allowclient", true);
|
|
30
25
|
const cborEncoder = lazy(() => new cbor.Encoder({ structuredClone: true }));
|
|
31
26
|
|
|
27
|
+
let onPredictionFinishedCallbacks: Array<(data: { callId: string; result: FunctionResult; functionId: string }) => void> = [];
|
|
28
|
+
|
|
29
|
+
export function onPredictionFinished(callback: (data: {
|
|
30
|
+
callId: string;
|
|
31
|
+
result: FunctionResult;
|
|
32
|
+
functionId: string;
|
|
33
|
+
}) => void) {
|
|
34
|
+
onPredictionFinishedCallbacks.push(callback);
|
|
35
|
+
}
|
|
32
36
|
|
|
33
37
|
// NOTE: Most functions won't use this, as they should use regular api calls. However,
|
|
34
38
|
// as we are using paths for RequireJS, we explicitly need our local (when serverside).
|
|
@@ -120,8 +124,11 @@ function predictCallBase(config: {
|
|
|
120
124
|
let functionResult: FunctionResult = {
|
|
121
125
|
// Special prediction values (we don't even really need to put anything here, but it
|
|
122
126
|
// might as well have the right format, in case a bug causes it to be used somewhere)
|
|
123
|
-
|
|
127
|
+
lastInternalLoopCount: -1,
|
|
128
|
+
outerLoopCount: -1,
|
|
129
|
+
totalInternalLoopCount: -1,
|
|
124
130
|
timeTaken: -1,
|
|
131
|
+
totalTime: -1,
|
|
125
132
|
evalTime: 0,
|
|
126
133
|
};
|
|
127
134
|
|
|
@@ -143,7 +150,6 @@ function predictCallBase(config: {
|
|
|
143
150
|
path: pathResultWrite,
|
|
144
151
|
// Technically we are depending on a future time, so... we are depending on ourself? And our value isn't undefined.
|
|
145
152
|
readIsTransparent: false,
|
|
146
|
-
keepRejectedUntil: call.runAtTime.time + CLIENTSIDE_PREDICT_LEEWAY
|
|
147
153
|
}
|
|
148
154
|
],
|
|
149
155
|
lockCount: 1,
|
|
@@ -169,9 +175,6 @@ function predictCallBase(config: {
|
|
|
169
175
|
console.log(magenta(`Start predict call`), `${call.DomainName}.${call.FunctionId}`);
|
|
170
176
|
}
|
|
171
177
|
|
|
172
|
-
// Watch the result, so we know when our prediction is rejected (which will be as soon as the result
|
|
173
|
-
// has a real value).
|
|
174
|
-
remoteWatcher.watchLatest({ paths: [pathResultWrite], parentPaths: [] });
|
|
175
178
|
let debugName = `[predict]|${call.DomainName}.${call.FunctionId}`;
|
|
176
179
|
|
|
177
180
|
let dryRunResult: {
|
|
@@ -182,8 +185,13 @@ function predictCallBase(config: {
|
|
|
182
185
|
|
|
183
186
|
let actualValueFinished = new PromiseObj();
|
|
184
187
|
function onActualFinished() {
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
let resultObj = authorityStorage.getValueAtTime(pathResultWrite, undefined);
|
|
189
|
+
let result = pathValueSerializer.getPathValue(resultObj) as FunctionResult | undefined;
|
|
190
|
+
if (!result) return;
|
|
191
|
+
if (result.lastInternalLoopCount === -1) return;
|
|
192
|
+
actualValueFinished.resolve();
|
|
193
|
+
for (let callback of onPredictionFinishedCallbacks) {
|
|
194
|
+
callback({ callId: call.CallId, result, functionId: call.FunctionId });
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
clientWatcher.setWatches({
|
|
@@ -191,40 +199,39 @@ function predictCallBase(config: {
|
|
|
191
199
|
paths: new Set([pathResultWrite]),
|
|
192
200
|
parentPaths: new Set(),
|
|
193
201
|
});
|
|
194
|
-
try {
|
|
195
202
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} catch (e: any) {
|
|
209
|
-
|
|
210
|
-
if (!pathValueSerializer.getPathValue(authorityStorage.getValueAtTime(pathResultWrite, undefined))) {
|
|
211
|
-
console.log(`Skipping prediction for ${debugName} due to error running predictive call. Likely just an out of order error.`, e.stack);
|
|
212
|
-
} else {
|
|
213
|
-
// NOTE: This case happens a lot, because of how we handle locks. We don't receive locked values, and so
|
|
214
|
-
// we assume all values have no locks, and only keep the latest. This is usually fine, but... if we lose
|
|
215
|
-
// the race to predict the function against the server updating it, it is likely our prediction will now
|
|
216
|
-
// be running before the latest write. In which case (as we don't really store write history), we will read undefined.
|
|
217
|
-
// This isn't accurate, but... our write WILL almost certainly be wrong (as the value changed), so we don't log here.
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
clientWatcher.unwatch(onActualFinished);
|
|
205
|
+
}, 30 * 1000);
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
let tempDryRunResult = await Promise.race([
|
|
209
|
+
await getCallWrites({ call, debugName, overrides: config.overrides, useFinishReordering: true, metadata: config.metadata }),
|
|
210
|
+
actualValueFinished.promise,
|
|
211
|
+
]);
|
|
212
|
+
if (!tempDryRunResult) {
|
|
213
|
+
if (Querysub.DEBUG_PREDICTIONS || Querysub.DEBUG_CALLS) {
|
|
214
|
+
console.log(magenta(`Abort predict call before prediction finished, already received call result`), `${call.DomainName}.${call.FunctionId}`);
|
|
218
215
|
}
|
|
219
|
-
return
|
|
220
|
-
writes: [],
|
|
221
|
-
readPaths: new Set(),
|
|
222
|
-
readParentPaths: new Set(),
|
|
223
|
-
replacedWriteValues: [],
|
|
224
|
-
};
|
|
216
|
+
return undefined;
|
|
225
217
|
}
|
|
226
|
-
|
|
227
|
-
|
|
218
|
+
dryRunResult = tempDryRunResult;
|
|
219
|
+
} catch (e: any) {
|
|
220
|
+
if (!pathValueSerializer.getPathValue(authorityStorage.getValueAtTime(pathResultWrite, undefined))) {
|
|
221
|
+
console.log(`Skipping prediction for ${debugName} due to error running predictive call. Likely just an out of order error.`, e.stack);
|
|
222
|
+
} else {
|
|
223
|
+
// NOTE: This case happens a lot, because of how we handle locks. We don't receive locked values, and so
|
|
224
|
+
// we assume all values have no locks, and only keep the latest. This is usually fine, but... if we lose
|
|
225
|
+
// the race to predict the function against the server updating it, it is likely our prediction will now
|
|
226
|
+
// be running before the latest write. In which case (as we don't really store write history), we will read undefined.
|
|
227
|
+
// This isn't accurate, but... our write WILL almost certainly be wrong (as the value changed), so we don't log here.
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
writes: [],
|
|
231
|
+
readPaths: new Set(),
|
|
232
|
+
readParentPaths: new Set(),
|
|
233
|
+
replacedWriteValues: [],
|
|
234
|
+
};
|
|
228
235
|
}
|
|
229
236
|
predictions = {
|
|
230
237
|
writes: dryRunResult.writes,
|
|
@@ -274,14 +281,11 @@ function predictCallBase(config: {
|
|
|
274
281
|
predict.lockCount = predictLocks.length;
|
|
275
282
|
}
|
|
276
283
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// - Also, once we set up this lock, we should get full reject cascade support.
|
|
283
|
-
lockToCallback.watchLock(predictResultWrite.locks[0], [predictResultWrite]);
|
|
284
|
-
lockToCallback.watchLock(predictLocks[0], predictions.writes);
|
|
284
|
+
validStateComputer.ingestValuesAndValidStates({
|
|
285
|
+
pathValues: predictions.writes,
|
|
286
|
+
parentSyncs: [],
|
|
287
|
+
initialTriggers: { values: new Set(), parentPaths: new Set() },
|
|
288
|
+
});
|
|
285
289
|
|
|
286
290
|
if (Querysub.DEBUG_PREDICTIONS) {
|
|
287
291
|
console.log(magenta(`Finished and applied predict call`), `${call.DomainName}.${call.FunctionId}`);
|
|
@@ -295,13 +299,19 @@ function predictCallBase(config: {
|
|
|
295
299
|
if (didCancel) return;
|
|
296
300
|
didCancel = true;
|
|
297
301
|
// Reject our predictions, as the call likely never got committed, so it will never be written
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
validStateComputer.ingestValuesAndValidStates({
|
|
303
|
+
pathValues: [{
|
|
304
|
+
path: pathResultWrite,
|
|
305
|
+
value: undefined,
|
|
306
|
+
locks: [],
|
|
307
|
+
lockCount: 0,
|
|
308
|
+
valid: false,
|
|
309
|
+
time: predictResultWrite.time,
|
|
310
|
+
isTransparent: false,
|
|
311
|
+
}],
|
|
312
|
+
parentSyncs: [],
|
|
313
|
+
initialTriggers: { values: new Set(), parentPaths: new Set() },
|
|
314
|
+
});
|
|
305
315
|
}
|
|
306
316
|
|
|
307
317
|
setTimeout(cleanupPrediction, Querysub.PREDICTION_MAX_LIFESPAN);
|
|
@@ -357,7 +367,7 @@ const getDevFunctionCache = cacheAsyncLimitedJSON(100_000, getDevFunctionSpecFro
|
|
|
357
367
|
const addToModuleLoaderCache = cacheAsyncLimitedJSON(100_000, addModuleToLoader);
|
|
358
368
|
|
|
359
369
|
function getFunctionSpec(call: CallSpec): FunctionSpec | undefined {
|
|
360
|
-
if (
|
|
370
|
+
if (!isPublic()) {
|
|
361
371
|
let obj = getDevFunctionCache({
|
|
362
372
|
DomainName: call.DomainName,
|
|
363
373
|
ModuleId: call.ModuleId,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { lazy } from "socket-function/src/caching";
|
|
2
|
-
import { LOCAL_DOMAIN } from "../0-path-value-core/NodePathAuthorities";
|
|
3
2
|
import { registerSchemaPrefix } from "../2-proxy/PathValueProxyWatcher";
|
|
4
3
|
import { rawSchema } from "../2-proxy/pathDatabaseProxyBase";
|
|
5
4
|
import { Schema2, Schema2T } from "../2-proxy/schema2";
|
|
@@ -7,6 +6,11 @@ import { isDynamicallyLoading } from "../config";
|
|
|
7
6
|
import { getPathStr2 } from "../path";
|
|
8
7
|
import { isHotReloading } from "socket-function/hot/HotReloadController";
|
|
9
8
|
import debugbreak from "debugbreak";
|
|
9
|
+
import { LOCAL_DOMAIN } from "../0-path-value-core/PathRouter";
|
|
10
|
+
if (!registerSchemaPrefix) {
|
|
11
|
+
debugbreak(2);
|
|
12
|
+
debugger;
|
|
13
|
+
}
|
|
10
14
|
|
|
11
15
|
let localSchemaNames = new Set<string>();
|
|
12
16
|
export function createLocalSchema<T>(name: string): () => T;
|
|
@@ -31,7 +31,7 @@ type StringFormatters = (
|
|
|
31
31
|
| "<Selector>"
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
-
function
|
|
34
|
+
function def(value: unknown, formattedValue: preact.ComponentChild) {
|
|
35
35
|
if (value === undefined || value === null) {
|
|
36
36
|
return "";
|
|
37
37
|
}
|
|
@@ -40,14 +40,14 @@ function d(value: unknown, formattedValue: preact.ComponentChild) {
|
|
|
40
40
|
const startGuessDateRange = +new Date(2010, 0, 1).getTime();
|
|
41
41
|
const endGuessDateRange = +new Date(2040, 0, 1).getTime();
|
|
42
42
|
let formatters: { [formatter in StringFormatters]: (value: unknown) => preact.ComponentChild } = {
|
|
43
|
-
string: (value) =>
|
|
44
|
-
number: (value) =>
|
|
45
|
-
percent: (value) =>
|
|
46
|
-
timeSpan: (value) =>
|
|
47
|
-
date: (value) =>
|
|
48
|
-
error: (value) =>
|
|
49
|
-
toSpaceCase: (value) =>
|
|
50
|
-
"<Selector>": (value) =>
|
|
43
|
+
string: (value) => def(value, String(value || "")),
|
|
44
|
+
number: (value) => def(value, formatNumber(Number(value))),
|
|
45
|
+
percent: (value) => def(value, formatPercent(Number(value))),
|
|
46
|
+
timeSpan: (value) => def(value, formatTime(Number(value))),
|
|
47
|
+
date: (value) => def(value, <span title={formatDateTimeDetailed(Number(value))}>{formatDateTime(Number(value))}</span>),
|
|
48
|
+
error: (value) => def(value, <span class={errorMessage}>{String(value)}</span>),
|
|
49
|
+
toSpaceCase: (value) => def(value, toSpaceCase(String(value))),
|
|
50
|
+
"<Selector>": (value) => def(value, <Selector {...JSON.parse(String(value).slice("<Selector>".length))} />),
|
|
51
51
|
link: (value) => {
|
|
52
52
|
if (value === undefined || value === null) {
|
|
53
53
|
return "";
|
|
@@ -118,6 +118,11 @@ let formatters: { [formatter in StringFormatters]: (value: unknown) => preact.Co
|
|
|
118
118
|
if (Array.isArray(value) && !value.some(x => canHaveChildren(x))) {
|
|
119
119
|
return formatValue(value, "varray:guess");
|
|
120
120
|
}
|
|
121
|
+
if (canHaveChildren(value)) {
|
|
122
|
+
try {
|
|
123
|
+
return JSON.stringify(value);
|
|
124
|
+
} catch { }
|
|
125
|
+
}
|
|
121
126
|
return formatters.string(value);
|
|
122
127
|
},
|
|
123
128
|
};
|
|
@@ -8,6 +8,10 @@ import { runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
|
8
8
|
|
|
9
9
|
import yargs from "yargs";
|
|
10
10
|
import { runAliveCheckerIteration } from "../2-proxy/garbageCollection";
|
|
11
|
+
import { waitForImportBlockers } from "../3-path-functions/pathFunctionLoader";
|
|
12
|
+
import { disablePathAuditer } from "../diagnostics/pathAuditerCallback";
|
|
13
|
+
import { authorityLookup } from "../0-path-value-core/AuthorityLookup";
|
|
14
|
+
import { getOurAuthoritySpec } from "../0-path-value-core/PathRouterServerAuthoritySpec";
|
|
11
15
|
|
|
12
16
|
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
13
17
|
.option("watch", { type: "boolean", desc: "Watch, and GC every N time (every day as of writing this)" })
|
|
@@ -16,12 +20,13 @@ let yargObj = isNodeTrue() && yargs(process.argv)
|
|
|
16
20
|
|
|
17
21
|
// yarn gc --authority testshard.json
|
|
18
22
|
async function main() {
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
disablePathAuditer();
|
|
21
24
|
let folderRoot = path.resolve(".");
|
|
22
25
|
folderRoot = folderRoot.replaceAll(/\\/g, "/");
|
|
23
26
|
const deployPath = path.resolve("./deploy.ts");
|
|
24
27
|
require(deployPath);
|
|
28
|
+
await waitForImportBlockers();
|
|
29
|
+
await Querysub.hostService("gc");
|
|
25
30
|
|
|
26
31
|
if (yargObj.watch) {
|
|
27
32
|
await runInfinitePollCallAtStart(timeInDay, runAliveCheckerIteration);
|
|
@@ -29,6 +34,8 @@ async function main() {
|
|
|
29
34
|
try {
|
|
30
35
|
// Force, as they are running this manually, and so they probably want to see something happen...
|
|
31
36
|
await runAliveCheckerIteration({ force: true });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(error);
|
|
32
39
|
} finally {
|
|
33
40
|
process.exit();
|
|
34
41
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import "../inject";
|
|
2
2
|
|
|
3
3
|
import { logErrors } from "../errors";
|
|
4
|
-
import { getOurAuthorities } from "../config2";
|
|
5
|
-
import { pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
6
4
|
import { PathValueArchives, pathValueArchives } from "../0-path-value-core/pathValueArchives";
|
|
7
5
|
import { FILE_SIZE_LIMIT, FILE_VALUE_COUNT_LIMIT, PathValue, VALUE_GC_THRESHOLD } from "../0-path-value-core/pathValueCore";
|
|
8
6
|
import { runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
@@ -13,111 +11,116 @@ import { formatNumber } from "socket-function/src/formatting/format";
|
|
|
13
11
|
import { sort } from "socket-function/src/misc";
|
|
14
12
|
import { Querysub } from "../4-querysub/QuerysubController";
|
|
15
13
|
import { magenta } from "socket-function/src/formatting/logColors";
|
|
14
|
+
import { PathRouter } from "../0-path-value-core/PathRouter";
|
|
15
|
+
import { disablePathAuditer } from "../diagnostics/pathAuditerCallback";
|
|
16
|
+
import { getOurAuthoritySpec } from "../0-path-value-core/PathRouterServerAuthoritySpec";
|
|
16
17
|
|
|
17
18
|
async function runGenesisJoinIteration() {
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let matchedDirs = await pathValueArchives.getAuthorityDirs(authority);
|
|
22
|
-
let authorityDir = pathValueAuthority2.getArchiveDirectory(authority);
|
|
23
|
-
let maxAge = Date.now() - VALUE_GC_THRESHOLD;
|
|
24
|
-
|
|
25
|
-
let readCache = new Map<string, Buffer>();
|
|
26
|
-
let reread = true;
|
|
27
|
-
while (reread) {
|
|
28
|
-
reread = false;
|
|
29
|
-
let result = await locker.atomicSwapFiles({}, async (valueFiles, readFiles) => {
|
|
30
|
-
let originalCount = valueFiles.length;
|
|
31
|
-
valueFiles = valueFiles.filter(x => matchedDirs.some(y => x.file.startsWith(y)));
|
|
32
|
-
let underPathCount = valueFiles.length;
|
|
33
|
-
// Merge the newest first, so if we hit a big file, we can just ignore it,
|
|
34
|
-
// and next time merge the smaller files after it.
|
|
35
|
-
sort(valueFiles, x => -x.createTime);
|
|
36
|
-
valueFiles = valueFiles.filter(x => {
|
|
37
|
-
let obj = pathValueArchives.decodeDataPath(x.file);
|
|
38
|
-
if (!obj.minTime) return false;
|
|
39
|
-
if (obj.sourceType !== "genesis") return false;
|
|
40
|
-
return x.createTime >= maxAge;
|
|
41
|
-
});
|
|
42
|
-
let withinTimeRangeCount = valueFiles.length;
|
|
43
|
-
|
|
44
|
-
if (valueFiles.length < 3) return [];
|
|
45
|
-
|
|
46
|
-
await measureBlock(async function locker_readFiles() {
|
|
47
|
-
let remainingFiles = valueFiles.filter(x => !readCache.has(x.file));
|
|
48
|
-
let buffers = await readFiles(remainingFiles);
|
|
49
|
-
for (let i = 0; i < remainingFiles.length; i++) {
|
|
50
|
-
let buffer = buffers[i];
|
|
51
|
-
if (!buffer) {
|
|
52
|
-
console.log(`File missing, re-reading`, remainingFiles[i].file);
|
|
53
|
-
reread = true;
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
readCache.set(remainingFiles[i].file, buffer);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
if (reread) {
|
|
60
|
-
console.log(`Files changed, re-reading`);
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
19
|
+
let authoritySpec = await getOurAuthoritySpec(true);
|
|
20
|
+
let locker = await pathValueArchives.getArchiveLocker();
|
|
21
|
+
let maxAge = Date.now() - VALUE_GC_THRESHOLD;
|
|
63
22
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
23
|
+
let readCache = new Map<string, Buffer>();
|
|
24
|
+
let reread = true;
|
|
25
|
+
while (reread) {
|
|
26
|
+
reread = false;
|
|
27
|
+
let result = await locker.atomicSwapFiles({}, async (valueFiles, readFiles) => {
|
|
28
|
+
let originalCount = valueFiles.length;
|
|
29
|
+
valueFiles = valueFiles.filter(x => PathRouter.overlapsPathIdentifier(authoritySpec, x.file));
|
|
30
|
+
let underPathCount = valueFiles.length;
|
|
31
|
+
// Merge the newest first, so if we hit a big file, we can just ignore it,
|
|
32
|
+
// and next time merge the smaller files after it.
|
|
33
|
+
sort(valueFiles, x => -x.createTime);
|
|
34
|
+
valueFiles = valueFiles.filter(x => {
|
|
35
|
+
let obj = pathValueArchives.decodeDataPath(x.file);
|
|
36
|
+
if (!obj.minTime) return false;
|
|
37
|
+
if (obj.sourceType !== "genesis") return false;
|
|
38
|
+
return x.createTime >= maxAge;
|
|
39
|
+
});
|
|
40
|
+
let withinTimeRangeCount = valueFiles.length;
|
|
41
|
+
|
|
42
|
+
if (valueFiles.length < 3) return [];
|
|
77
43
|
|
|
78
|
-
|
|
79
|
-
|
|
44
|
+
await measureBlock(async function locker_readFiles() {
|
|
45
|
+
let remainingFiles = valueFiles.filter(x => !readCache.has(x.file));
|
|
46
|
+
let buffers = await readFiles(remainingFiles);
|
|
47
|
+
for (let i = 0; i < remainingFiles.length; i++) {
|
|
48
|
+
let buffer = buffers[i];
|
|
49
|
+
if (!buffer) {
|
|
50
|
+
console.log(`File missing, re-reading`, remainingFiles[i].file);
|
|
51
|
+
reread = true;
|
|
80
52
|
break;
|
|
81
53
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
54
|
+
readCache.set(remainingFiles[i].file, buffer);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
if (reread) {
|
|
58
|
+
console.log(`Files changed, re-reading`);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let usedFiles: FileInfo[] = [];
|
|
63
|
+
let totalSize = 0;
|
|
64
|
+
let totalCount = 0;
|
|
65
|
+
let allValues: PathValue[][] = [];
|
|
66
|
+
for (let valueFile of valueFiles) {
|
|
67
|
+
let buffer = readCache.get(valueFile.file)!;
|
|
68
|
+
if (totalSize + buffer.length > FILE_SIZE_LIMIT) {
|
|
69
|
+
break;
|
|
86
70
|
}
|
|
87
|
-
|
|
71
|
+
let newValues = await PathValueArchives.loadValuesFromBuffer({
|
|
72
|
+
path: valueFile.file,
|
|
73
|
+
data: buffer,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let readValues = newValues.values;
|
|
77
|
+
if (readValues.length + totalCount > FILE_VALUE_COUNT_LIMIT) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
allValues.push(readValues);
|
|
81
|
+
usedFiles.push(valueFile);
|
|
82
|
+
totalSize += buffer.length;
|
|
83
|
+
totalCount += readValues.length;
|
|
84
|
+
}
|
|
85
|
+
if (usedFiles.length <= 1) return [];
|
|
86
|
+
|
|
87
|
+
let allCombinedValues = allValues.flat();
|
|
88
88
|
|
|
89
|
-
let allCombinedValues = allValues.flat();
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
let transaction: ArchiveTransaction = {
|
|
91
|
+
createFiles: [],
|
|
92
|
+
deleteFiles: [],
|
|
93
|
+
};
|
|
94
|
+
for (let file of usedFiles) {
|
|
95
|
+
transaction.deleteFiles.push(file);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let targets = PathRouter.getSelfPathIdentifierTargets(allCombinedValues);
|
|
99
|
+
for (let [target, values] of targets) {
|
|
100
|
+
|
|
101
|
+
let dataObj = await pathValueArchives.encodeValuePaths(values, {
|
|
92
102
|
pathOverrides: {
|
|
93
103
|
sourceType: "join",
|
|
94
104
|
},
|
|
95
105
|
});
|
|
96
|
-
if (!dataObj)
|
|
97
|
-
let
|
|
98
|
-
createFiles: [],
|
|
99
|
-
deleteFiles: [],
|
|
100
|
-
};
|
|
101
|
-
for (let file of usedFiles) {
|
|
102
|
-
transaction.deleteFiles.push(file);
|
|
103
|
-
}
|
|
104
|
-
let newFileName = authorityDir + dataObj?.file;
|
|
106
|
+
if (!dataObj) continue;
|
|
107
|
+
let newFileName = target + "/" + dataObj?.file;
|
|
105
108
|
transaction.createFiles.push({
|
|
106
109
|
file: newFileName,
|
|
107
110
|
data: dataObj.data,
|
|
108
111
|
});
|
|
112
|
+
}
|
|
109
113
|
|
|
114
|
+
console.log(magenta(`Joining ${formatNumber(usedFiles.length)} files with ${formatNumber(allCombinedValues.length)} values in ${formatNumber(totalSize)} bytes. Original count: ${formatNumber(originalCount)}, under path count: ${formatNumber(underPathCount)}, within time range count: ${formatNumber(withinTimeRangeCount)}`));
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
115
|
-
console.log(`Join result: ${result}`);
|
|
116
|
-
}
|
|
116
|
+
return [transaction];
|
|
117
|
+
});
|
|
118
|
+
console.log(`Join result: ${result}`);
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
async function main() {
|
|
123
|
+
disablePathAuditer();
|
|
121
124
|
await Querysub.hostService("join");
|
|
122
125
|
|
|
123
126
|
await runInfinitePollCallAtStart(VALUE_GC_THRESHOLD * 0.8, runGenesisJoinIteration);
|
|
@@ -5,6 +5,7 @@ import { logErrors } from "../errors";
|
|
|
5
5
|
import { timeInDay, timeInHour } from "socket-function/src/misc";
|
|
6
6
|
import { runArchiveMover } from "../2-proxy/archiveMoveHarness";
|
|
7
7
|
import { PathValue, compareTime } from "../0-path-value-core/pathValueCore";
|
|
8
|
+
import { disablePathAuditer } from "../diagnostics/pathAuditerCallback";
|
|
8
9
|
|
|
9
10
|
const MERGE_DELAY = timeInHour;
|
|
10
11
|
|
|
@@ -34,6 +35,7 @@ async function mergeFiles() {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
async function main() {
|
|
38
|
+
disablePathAuditer();
|
|
37
39
|
await Querysub.hostService("merge");
|
|
38
40
|
await mergeFiles();
|
|
39
41
|
process.exit();
|
package/src/bits.ts
CHANGED
|
@@ -53,6 +53,25 @@ export function setLowHighUint32(low: number, high: number): number {
|
|
|
53
53
|
return conversionBuffer[0];
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** Adds protection against NaN values, changing the result if it would be NaN. This is because NaN values will compare to be not equal even if they are equal in certain cases, and in other cases, they'll always be equal, even if their bits are not equal.
|
|
57
|
+
- This means this is not a reversible operation. However, in a lot of cases, that doesn't matter.
|
|
58
|
+
*/
|
|
59
|
+
export function setLowHighUint32Safe(low: number, high: number): number {
|
|
60
|
+
conversionUint32Buffer[0] = low;
|
|
61
|
+
// Prevent NaN by not setting all the exponent bits to 1
|
|
62
|
+
conversionUint32Buffer[1] = high & 0xBFFFFFFF;
|
|
63
|
+
return conversionBuffer[0];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// NOTE: Not reversible, see setLowHighUint32Safe
|
|
67
|
+
export function xor64BitsSafe(a: number, b: number): number {
|
|
68
|
+
let high = getHighUint32(a);
|
|
69
|
+
let low = getLowUint32(a);
|
|
70
|
+
let high2 = getHighUint32(b);
|
|
71
|
+
let low2 = getLowUint32(b);
|
|
72
|
+
return setLowHighUint32Safe(low ^ low2, high ^ high2);
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
// Gets bits that can be stored in a number. Specifically, the first 62 bits,
|
|
57
76
|
// as 64 bits will not compare correctly when treated as a double.
|
|
58
77
|
export function getShortNumber(buffer: Buffer): number {
|
package/src/config.ts
CHANGED
|
@@ -13,7 +13,9 @@ let yargObj = parseArgsFactory()
|
|
|
13
13
|
.option("domain", { type: "string", desc: `Sets the domain` })
|
|
14
14
|
.option("emaildomain", { type: "string", desc: `Sets the domain to use for email` })
|
|
15
15
|
.option("client", { type: "boolean", desc: `Drops permissions, acting as an unauthenticated node` })
|
|
16
|
-
.option("
|
|
16
|
+
.option("arange", { type: "string", desc: `Authority range. Ex, 0.5-1.0` })
|
|
17
|
+
.option("aexcludedefault", { type: "boolean", desc: `Exclude default authority values, so we only match our prefixes.` })
|
|
18
|
+
.option("aprefix", { type: "array", desc: `Override the prefixes (pass multiple arguments to add multiple). Can be used with aexcludedefault to restrict an authority to only a few specific prefixes (ex, ` })
|
|
17
19
|
.option("nobreak", { type: "boolean", desc: "Do not break on errors. Safer to set this than to just not set debugbreak, as some places might break without checking debugbreak, but nobreak works at a level where it is always used." })
|
|
18
20
|
.option("public", { type: "boolean", desc: "Expose on public ports." })
|
|
19
21
|
.option("local", { type: "boolean", desc: `If true, uses the local directory instead of the remote git repo. Also hotreloads from disk. Determines the repo to replace through the package.json "repository" property.` })
|
|
@@ -27,6 +29,7 @@ let yargObj = parseArgsFactory()
|
|
|
27
29
|
// NOTE: I wanna see how long I can keep this on for. Eventually it's gonna become a problem and we're gonna have to turn it off. But for testing it's certainly useful as we don't know exactly what is gonna cause a problem. But it probably will be synchronization related, and every server does synchronization.
|
|
28
30
|
desc: "Track all audit logs to disk. This might end up writing A LOT of data."
|
|
29
31
|
})
|
|
32
|
+
.option("logbackblaze", { type: "boolean", desc: "Log all backblaze activity to disk." })
|
|
30
33
|
.argv
|
|
31
34
|
;
|
|
32
35
|
type QuerysubConfig = {
|
|
@@ -48,6 +51,10 @@ let querysubConfig = lazy((): QuerysubConfig => {
|
|
|
48
51
|
}
|
|
49
52
|
});
|
|
50
53
|
|
|
54
|
+
export function isLogBackblaze() {
|
|
55
|
+
return !!yargObj.logbackblaze;
|
|
56
|
+
}
|
|
57
|
+
|
|
51
58
|
export function isNoNetwork() {
|
|
52
59
|
return yargObj.nonetwork;
|
|
53
60
|
}
|
|
@@ -102,8 +109,18 @@ export function enableDebugging() {
|
|
|
102
109
|
(globalThis as any).enableDebugging = enableDebugging;
|
|
103
110
|
|
|
104
111
|
|
|
105
|
-
export function
|
|
106
|
-
return yargObj.
|
|
112
|
+
export function getAuthorityRange() {
|
|
113
|
+
return yargObj.arange;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getAuthorityExcludeDefault() {
|
|
117
|
+
return !!yargObj.aexcludedefault;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getAuthorityPrefix() {
|
|
121
|
+
if (!yargObj.aprefix) return undefined;
|
|
122
|
+
if (Array.isArray(yargObj.aprefix)) return yargObj.aprefix;
|
|
123
|
+
return [yargObj.aprefix];
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
export function isPublic() {
|
|
@@ -113,6 +130,7 @@ export function isPublic() {
|
|
|
113
130
|
return !!yargObj.public;
|
|
114
131
|
}
|
|
115
132
|
|
|
133
|
+
/** @deprecated Use !isPublic() instead */
|
|
116
134
|
export function isLocal() {
|
|
117
135
|
return !!yargObj.local;
|
|
118
136
|
}
|