querysub 0.402.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 -1492
- package/src/0-path-value-core/startupAuthority.ts +74 -0
- package/src/1-path-client/RemoteWatcher.ts +100 -83
- 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 +39 -17
- 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
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { delay } from "socket-function/src/batching";
|
|
2
|
+
import { DISK_FLUSH_INTERVAL } from "../diagnostics/logs/IndexedLogs/BufferIndexLogsOptimizationConstants";
|
|
3
|
+
import { authorityLookup } from "./AuthorityLookup";
|
|
4
|
+
import { AuthoritySpec, PathRouter } from "./PathRouter";
|
|
5
|
+
import { PathValueControllerBase, pathValueCommitter } from "./PathValueController";
|
|
6
|
+
import { validStateComputer } from "./ValidStateComputer";
|
|
7
|
+
import { MAX_TIME_UNTIL_DISK_FLUSH, authorityStorage, pathValueArchives } from "./pathValueCore";
|
|
8
|
+
import { blue, green } from "socket-function/src/formatting/logColors";
|
|
9
|
+
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
10
|
+
|
|
11
|
+
const MAX_LOAD_RETRIES = 3;
|
|
12
|
+
export async function startupAuthority(spec: AuthoritySpec) {
|
|
13
|
+
let startTime = Date.now();
|
|
14
|
+
console.log(`Becoming authority for`, { spec });
|
|
15
|
+
|
|
16
|
+
// NOTE: This will broadcast all the other servers about our spec, and so all other servers will start giving us live writes.
|
|
17
|
+
await authorityLookup.setOurSpec(spec);
|
|
18
|
+
|
|
19
|
+
let readStartTime = Date.now() - MAX_TIME_UNTIL_DISK_FLUSH;
|
|
20
|
+
let readEndTime = Number.MAX_SAFE_INTEGER;
|
|
21
|
+
|
|
22
|
+
let sources = await PathRouter.getAuthoritySources({ target: spec });
|
|
23
|
+
console.log(`Found sources when finding spec, count: ${sources.length}`, { spec, sources });
|
|
24
|
+
async function loadFromSourceBase(source: AuthoritySpec) {
|
|
25
|
+
console.log(blue(`Loading values from source ${source.nodeId}`), { nodeId: source.nodeId, spec: source, readStartTime, readEndTime });
|
|
26
|
+
let values = await PathValueControllerBase.getInitialValues({
|
|
27
|
+
nodeId: source.nodeId,
|
|
28
|
+
spec: source,
|
|
29
|
+
startTime: readStartTime,
|
|
30
|
+
endTime: readEndTime,
|
|
31
|
+
});
|
|
32
|
+
console.log(blue(`Finished loading values from source ${source.nodeId}, found ${formatNumber(values.length)} values`), { nodeId: source.nodeId, spec: source, values: values.length });
|
|
33
|
+
validStateComputer.ingestValuesAndValidStates({
|
|
34
|
+
pathValues: values,
|
|
35
|
+
parentSyncs: [],
|
|
36
|
+
initialTriggers: { values: new Set(), parentPaths: new Set() },
|
|
37
|
+
doNotArchive: true,
|
|
38
|
+
});
|
|
39
|
+
console.log(blue(`Finished ingesting values from source ${source.nodeId} (values: ${formatNumber(values.length)}`), { nodeId: source.nodeId, spec: source, values: values.length });
|
|
40
|
+
}
|
|
41
|
+
async function loadFromSource(source: AuthoritySpec, retries = 3) {
|
|
42
|
+
try {
|
|
43
|
+
await loadFromSourceBase(source);
|
|
44
|
+
return;
|
|
45
|
+
} catch (error: any) {
|
|
46
|
+
console.error(`Failed to load from source ${source.nodeId}`, { error: error.stack });
|
|
47
|
+
await delay(1000);
|
|
48
|
+
if (retries > 0) {
|
|
49
|
+
let sources = await PathRouter.getAuthoritySources({ target: source });
|
|
50
|
+
await Promise.all(sources.map(x => loadFromSource(x, retries - 1)));
|
|
51
|
+
} else {
|
|
52
|
+
console.error(`Failed to load from source ${source.nodeId} after ${MAX_LOAD_RETRIES} retries`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let sourcePromises = sources.map(x => loadFromSource(x));
|
|
58
|
+
|
|
59
|
+
// Oh, we need to load snapshots as well... of course
|
|
60
|
+
let diskPromise = (async () => {
|
|
61
|
+
let snapshot = await pathValueArchives.loadValues(spec);
|
|
62
|
+
let flat = Object.values(snapshot.values).flat();
|
|
63
|
+
console.log(blue(`Loaded snapshot in memory, found ${formatNumber(flat.length)} values`), { spec, values: flat.length });
|
|
64
|
+
// NOTE: If it's on disk, it can't be rejected, so there's no point in passing it through our validStateComputer (and passing it through is VERY slow).
|
|
65
|
+
authorityStorage.ingestValues(flat, { doNotArchive: true });
|
|
66
|
+
console.log(blue(`Finished ingesting values from snapshot, found ${formatNumber(flat.length)} values`), { spec, values: flat.length });
|
|
67
|
+
})();
|
|
68
|
+
|
|
69
|
+
await Promise.all([...sourcePromises, diskPromise]);
|
|
70
|
+
|
|
71
|
+
await authorityLookup.setIsReady();
|
|
72
|
+
let duration = Date.now() - startTime;
|
|
73
|
+
console.log(green(`Finished becoming authority, took ${formatTime(duration)}`), { spec });
|
|
74
|
+
}
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
|
|
2
2
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
3
|
-
import { batchFunction,
|
|
3
|
+
import { batchFunction, runInSerial, runInfinitePoll } from "socket-function/src/batching";
|
|
4
4
|
import { cache } from "socket-function/src/caching";
|
|
5
|
-
import { isNode
|
|
5
|
+
import { isNode } from "socket-function/src/misc";
|
|
6
6
|
import { measureFnc, measureBlock } from "socket-function/src/profiling/measure";
|
|
7
|
-
import { isOwnNodeId
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { WatchConfig, authorityStorage
|
|
7
|
+
import { isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
8
|
+
import { PathValueController } from "../0-path-value-core/PathValueController";
|
|
9
|
+
import { PathRouter } from "../0-path-value-core/PathRouter";
|
|
10
|
+
import { WatchConfig, authorityStorage } from "../0-path-value-core/pathValueCore";
|
|
11
|
+
import { pathWatcher } from "../0-path-value-core/PathWatcher";
|
|
11
12
|
import { ActionsHistory } from "../diagnostics/ActionsHistory";
|
|
12
13
|
import { registerResource } from "../diagnostics/trackResources";
|
|
13
14
|
import { logErrors } from "../errors";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { appendToPathStr, getParentPathStr, getPathDepth, getPathIndexAssert, getPathSuffix, hack_getPackedPathSuffix, hack_setPackedPathSuffix } from "../path";
|
|
15
|
+
import { green, yellow } from "socket-function/src/formatting/logColors";
|
|
16
|
+
import { getParentPathStr } from "../path";
|
|
17
17
|
import { isEmpty } from "../misc";
|
|
18
|
-
import debugbreak from "debugbreak";
|
|
19
18
|
import { isDevDebugbreak } from "../config";
|
|
20
|
-
import { auditLog } from "../0-path-value-core/auditLogs";
|
|
19
|
+
import { auditLog, isDebugLogEnabled } from "../0-path-value-core/auditLogs";
|
|
20
|
+
import { debugNodeThread } from "../-c-identity/IdentityController";
|
|
21
|
+
import { authorityLookup } from "../0-path-value-core/AuthorityLookup";
|
|
22
|
+
import { decodeParentFilter, encodeParentFilter } from "../0-path-value-core/hackedPackedPathParentFiltering";
|
|
23
|
+
setImmediate(() => import("../4-querysub/Querysub"));
|
|
21
24
|
|
|
22
25
|
// NOTE: In some cases this can half our the PathValues sent. AND, it can reduce our sync requests by N, and as those are uploads those are event more expensive. However, it also complicates other code, requiring not just checking if a value is synced, but also the parent path. We should try to make this stable, but worst case scenario, we can set this to false, and it will make our synchronization a lot easier to verify.
|
|
23
26
|
const STOP_KEYS_DOUBLE_SENDS = true;
|
|
24
27
|
|
|
28
|
+
// NOTE: If a parent watch is broken up between multiple nodes, we generally watch everything on all those nodes and then filter when we receive the data. This isn't efficient, and we should probably change it. However, in practice, it's probably fine, as it's unlikely for the watches to be broken up to a much finer granularity than the network (it would require very strange partially overlapping sharding cases which no reasonable sharding setup would ever satisfy).
|
|
25
29
|
export class RemoteWatcher {
|
|
26
30
|
public static DEBUG = false;
|
|
27
31
|
|
|
@@ -31,7 +35,11 @@ export class RemoteWatcher {
|
|
|
31
35
|
// path => nodeId
|
|
32
36
|
// isOwnNodeId(nodeId) means we are watching it locally
|
|
33
37
|
private remoteWatchPaths = registerResource("paths|remoteWatchPaths", new Map<string, string>());
|
|
38
|
+
// inputPath =>
|
|
34
39
|
private remoteWatchParents = registerResource("paths|remoteWatchParents", new Map<string, {
|
|
40
|
+
useFullPathHash: boolean;
|
|
41
|
+
// Might vary from the input path if useFullPathHash is true (and the path is a hacked path, and so which has a range restriction).
|
|
42
|
+
watchedPath: string;
|
|
35
43
|
nodesId: Map<string, {
|
|
36
44
|
start: number;
|
|
37
45
|
end: number;
|
|
@@ -115,12 +123,13 @@ export class RemoteWatcher {
|
|
|
115
123
|
public debugIsWatchingPath(path: string) {
|
|
116
124
|
// HACK: If there is no read node... pretend we are watching it. Because... for now, this is a known
|
|
117
125
|
// and the code calling this function isn't looking for that specific issue.
|
|
118
|
-
return this.remoteWatchPaths.has(path) || !
|
|
126
|
+
return this.remoteWatchPaths.has(path) || !PathRouter.getReadyAuthority(path);
|
|
119
127
|
}
|
|
120
128
|
public debugIsWatchingParentPath(path: string) {
|
|
121
|
-
return this.remoteWatchParents.has(path) || !
|
|
129
|
+
return this.remoteWatchParents.has(path) || !PathRouter.getReadyAuthority(path);
|
|
122
130
|
}
|
|
123
131
|
|
|
132
|
+
/** @deprecated If you want to watch something, call pathWatcher.watchPath instead. */
|
|
124
133
|
public watchLatest(config: WatchConfig & { debugName?: string }) {
|
|
125
134
|
logErrors(this.watchLatestPromise(config));
|
|
126
135
|
}
|
|
@@ -130,10 +139,9 @@ export class RemoteWatcher {
|
|
|
130
139
|
// 2) If it errors out, they can't do anything to fix the error
|
|
131
140
|
// 3) We retry internally anyways
|
|
132
141
|
private watchLatestPromise(config: WatchConfig & { debugName?: string }) {
|
|
133
|
-
// NOTE: If none of the values are remote... early out. This has been profiled to save a bit of time,
|
|
134
|
-
// mostly due to avoiding the async call.
|
|
142
|
+
// NOTE: If none of the values are remote... early out. This has been profiled to save a bit of time, mostly due to avoiding the async call.
|
|
135
143
|
if (config.parentPaths.length === 0) {
|
|
136
|
-
let isSelf = measureBlock(() => config.paths.every(x =>
|
|
144
|
+
let isSelf = measureBlock(() => config.paths.every(x => PathRouter.isSelfAuthority(x)), "RemoteWatcher()|watchLatestPromise|isSelfCheck");
|
|
137
145
|
if (isSelf) {
|
|
138
146
|
return;
|
|
139
147
|
}
|
|
@@ -154,7 +162,7 @@ export class RemoteWatcher {
|
|
|
154
162
|
/** NOTE: We dedupe duplicate watches in watchLatest. */
|
|
155
163
|
private async watchLatestBase(config: WatchConfig & { debugName?: string }) {
|
|
156
164
|
await this.watchUnwatchSerial("watchLatestBase", async () => {
|
|
157
|
-
await
|
|
165
|
+
await authorityLookup.startSyncing();
|
|
158
166
|
|
|
159
167
|
this.internalWatchLatest(config);
|
|
160
168
|
});
|
|
@@ -180,7 +188,7 @@ export class RemoteWatcher {
|
|
|
180
188
|
measureBlock(() => {
|
|
181
189
|
for (let path of config.paths) {
|
|
182
190
|
// NOTE: We weren't ignoring local paths here for a while, but... I'm pretty sure we should, to save time.
|
|
183
|
-
if (
|
|
191
|
+
if (PathRouter.isLocalPath(path)) continue;
|
|
184
192
|
|
|
185
193
|
// IMPORTANT! We have to await, otherwise we might see the paths are watched, skip them
|
|
186
194
|
// find another path which has an authority, then go to watch that... which causes
|
|
@@ -200,10 +208,11 @@ export class RemoteWatcher {
|
|
|
200
208
|
continue;
|
|
201
209
|
}
|
|
202
210
|
|
|
203
|
-
let authorityId =
|
|
211
|
+
let authorityId = PathRouter.getReadyAuthority(path)?.nodeId;
|
|
204
212
|
if (!authorityId) {
|
|
205
213
|
if (totalMissingPaths === 0) {
|
|
206
|
-
let authorities =
|
|
214
|
+
let authorities = authorityLookup.getTopologySync();
|
|
215
|
+
authorityId = PathRouter.getReadyAuthority(path)?.nodeId;
|
|
207
216
|
console.warn(`Missing authority for path ${path}`, { authorities });
|
|
208
217
|
}
|
|
209
218
|
totalMissingPaths++;
|
|
@@ -227,29 +236,53 @@ export class RemoteWatcher {
|
|
|
227
236
|
paths.paths.push(path);
|
|
228
237
|
}
|
|
229
238
|
|
|
230
|
-
for (let
|
|
239
|
+
for (let originalPath of config.parentPaths) {
|
|
231
240
|
// NOTE: We weren't ignoring local paths here for a while, but... I'm pretty sure we should, to save time.
|
|
232
|
-
if (
|
|
233
|
-
let watchObj = this.remoteWatchParents.get(
|
|
241
|
+
if (PathRouter.isLocalPath(originalPath)) continue;
|
|
242
|
+
let watchObj = this.remoteWatchParents.get(originalPath);
|
|
234
243
|
if (
|
|
235
244
|
watchObj
|
|
236
245
|
&& watchObj.nodesId.size === watchObj.fullNodeCount
|
|
237
246
|
&& !config.tryReconnect
|
|
238
247
|
) continue;
|
|
239
248
|
|
|
240
|
-
let { nodes } =
|
|
249
|
+
let { useFullPathHash, nodes } = PathRouter.getChildReadNodes(originalPath, {
|
|
241
250
|
// Pass existing connected node ids as preferred
|
|
242
|
-
preferredNodeIds: watchObj
|
|
251
|
+
preferredNodeIds: watchObj && Array.from(watchObj.nodesId.keys())
|
|
243
252
|
});
|
|
253
|
+
let watchedPath = originalPath;
|
|
254
|
+
useFullPathHash = !!useFullPathHash;
|
|
255
|
+
let decodedParentFilter = decodeParentFilter(originalPath);
|
|
256
|
+
if (useFullPathHash && decodedParentFilter) {
|
|
257
|
+
watchedPath = encodeParentFilter({
|
|
258
|
+
path: decodedParentFilter.path,
|
|
259
|
+
startFraction: decodedParentFilter.start,
|
|
260
|
+
endFraction: decodedParentFilter.end,
|
|
261
|
+
useFullPathHash: true,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Unwatch the old watchedPath
|
|
266
|
+
if (watchObj && watchObj.watchedPath !== watchedPath) {
|
|
267
|
+
for (let nodeId of watchObj.nodesId.keys()) {
|
|
268
|
+
let parentPaths = unwatchParentsPerAuthority.get(nodeId);
|
|
269
|
+
if (!parentPaths) {
|
|
270
|
+
parentPaths = new Set();
|
|
271
|
+
unwatchParentsPerAuthority.set(nodeId, parentPaths);
|
|
272
|
+
}
|
|
273
|
+
parentPaths.add(watchObj.watchedPath);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
watchObj = undefined;
|
|
277
|
+
}
|
|
244
278
|
|
|
245
279
|
let rangeStart = 0;
|
|
246
280
|
let rangeEnd = 1;
|
|
247
281
|
|
|
248
282
|
let nodesToAdd = new Map<string, { start: number; end: number }>();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
rangeEnd = range.end;
|
|
283
|
+
if (decodedParentFilter) {
|
|
284
|
+
rangeStart = decodedParentFilter.start;
|
|
285
|
+
rangeEnd = decodedParentFilter.end;
|
|
253
286
|
}
|
|
254
287
|
|
|
255
288
|
{
|
|
@@ -257,7 +290,7 @@ export class RemoteWatcher {
|
|
|
257
290
|
for (let node of nodes) {
|
|
258
291
|
let r = node.range;
|
|
259
292
|
if (r.start > curStart) {
|
|
260
|
-
console.warn(`There is a gap in the range of nodes for path ${
|
|
293
|
+
console.warn(`There is a gap in the range of nodes for path ${originalPath} values from from ${curStart} to ${r.start} cannot be found in any nodes.`);
|
|
261
294
|
curStart = r.start;
|
|
262
295
|
}
|
|
263
296
|
let nextEnd = Math.min(r.end, rangeEnd);
|
|
@@ -284,15 +317,16 @@ export class RemoteWatcher {
|
|
|
284
317
|
}
|
|
285
318
|
}
|
|
286
319
|
if (!fullRangeCovered) {
|
|
287
|
-
if (!this.disconnectedParents.has(
|
|
320
|
+
if (!this.disconnectedParents.has(originalPath)) {
|
|
288
321
|
newDisconnectParents++;
|
|
289
|
-
this.disconnectedParents.add(
|
|
322
|
+
this.disconnectedParents.add(originalPath);
|
|
290
323
|
}
|
|
291
324
|
} else {
|
|
292
|
-
this.disconnectedParents.delete(
|
|
325
|
+
this.disconnectedParents.delete(originalPath);
|
|
293
326
|
foundParentPaths++;
|
|
294
327
|
}
|
|
295
328
|
|
|
329
|
+
// Unwatch nodes that have changed
|
|
296
330
|
if (watchObj) {
|
|
297
331
|
for (let nodeId of watchObj.nodesId.keys()) {
|
|
298
332
|
if (!nodesToAdd.has(nodeId)) {
|
|
@@ -301,28 +335,29 @@ export class RemoteWatcher {
|
|
|
301
335
|
parentPaths = new Set();
|
|
302
336
|
unwatchParentsPerAuthority.set(nodeId, parentPaths);
|
|
303
337
|
}
|
|
304
|
-
parentPaths.add(
|
|
338
|
+
parentPaths.add(watchedPath);
|
|
305
339
|
watchObj.nodesId.delete(nodeId);
|
|
306
340
|
}
|
|
307
341
|
}
|
|
308
342
|
}
|
|
309
343
|
|
|
310
344
|
if (!watchObj) {
|
|
311
|
-
watchObj = { nodesId: new Map(), fullNodeCount: 0, suppressedWatches: new Set() };
|
|
312
|
-
this.remoteWatchParents.set(
|
|
345
|
+
watchObj = { nodesId: new Map(), fullNodeCount: 0, suppressedWatches: new Set(), watchedPath, useFullPathHash };
|
|
346
|
+
this.remoteWatchParents.set(watchedPath, watchObj);
|
|
313
347
|
}
|
|
314
348
|
watchObj.fullNodeCount = nodes.length;
|
|
315
349
|
for (let [nodeId, range] of nodesToAdd) {
|
|
316
350
|
watchObj.nodesId.set(nodeId, range);
|
|
317
351
|
}
|
|
318
352
|
|
|
353
|
+
// NOTE: We watch every node with the full request. If our nodes are sharded well, this should be fine. They won't have that much data to give us, and we'll ignore it when we receive it if it's more than we want.
|
|
319
354
|
for (let authorityId of nodesToAdd.keys()) {
|
|
320
355
|
let paths = watchesPerAuthority.get(authorityId);
|
|
321
356
|
if (!paths) {
|
|
322
357
|
paths = { paths: [], parentPaths: [] };
|
|
323
358
|
watchesPerAuthority.set(authorityId, paths);
|
|
324
359
|
}
|
|
325
|
-
paths.parentPaths.push(
|
|
360
|
+
paths.parentPaths.push(watchedPath);
|
|
326
361
|
}
|
|
327
362
|
}
|
|
328
363
|
}, `watchLatest|getSingleReadNode`);
|
|
@@ -350,22 +385,12 @@ export class RemoteWatcher {
|
|
|
350
385
|
// Only remote watch remotes!
|
|
351
386
|
if (isOwnNodeId(authorityId)) continue;
|
|
352
387
|
|
|
353
|
-
if (RemoteWatcher.DEBUG) {
|
|
354
|
-
console.log(`${blue("Watching")} ${paths.length} paths and ${parentPaths.length} parent paths on ${authorityId}\n\t authority: ${blue(nodePathAuthority.getAuthorityPaths(authorityId)?.map(x => nodePathAuthority.getArchiveDirectory(x)).join(" | ") || "")}`);
|
|
355
|
-
if (parentPaths.length > 0) {
|
|
356
|
-
console.log(`\t parentPaths:`);
|
|
357
|
-
for (let path of parentPaths) {
|
|
358
|
-
console.log(`\t ${path}`);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
388
|
if (isDevDebugbreak()) {
|
|
364
389
|
for (let path of paths) {
|
|
365
390
|
auditLog("remoteWatcher outer WATCH", { path, remoteNodeId: authorityId });
|
|
366
391
|
}
|
|
367
392
|
for (let path of parentPaths) {
|
|
368
|
-
auditLog("remoteWatcher outer WATCH
|
|
393
|
+
auditLog("remoteWatcher outer PARENT WATCH", { path, remoteNodeId: authorityId });
|
|
369
394
|
}
|
|
370
395
|
}
|
|
371
396
|
|
|
@@ -397,31 +422,10 @@ export class RemoteWatcher {
|
|
|
397
422
|
}
|
|
398
423
|
}
|
|
399
424
|
|
|
400
|
-
public getChildPatchWatchChecker(config: {
|
|
401
|
-
parentPath: string;
|
|
402
|
-
nodeId: string;
|
|
403
|
-
}): {
|
|
404
|
-
isWatchedByNodeId(childPath: string): boolean;
|
|
405
|
-
} {
|
|
406
|
-
let remoteNodes = this.remoteWatchParents.get(config.parentPath);
|
|
407
|
-
if (!remoteNodes) return { isWatchedByNodeId: () => false };
|
|
408
|
-
const range = remoteNodes.nodesId.get(config.nodeId);
|
|
409
|
-
if (!range) return { isWatchedByNodeId: () => false };
|
|
410
|
-
return {
|
|
411
|
-
isWatchedByNodeId(childPath) {
|
|
412
|
-
return matchesParentRangeFilter({
|
|
413
|
-
parentPath: config.parentPath,
|
|
414
|
-
fullPath: childPath,
|
|
415
|
-
start: range.start,
|
|
416
|
-
end: range.end,
|
|
417
|
-
});
|
|
418
|
-
},
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
425
|
private watchLatestRemote = batchFunction(
|
|
423
426
|
{ delay: 10, throttleWindow: isNode() ? 500 : undefined },
|
|
424
427
|
async (batched: (WatchConfig & { authorityId: string; debugName?: string })[]) => {
|
|
428
|
+
const { Querysub } = await import("../4-querysub/Querysub");
|
|
425
429
|
await this.watchUnwatchSerial("watchLatestRemote", async () => {
|
|
426
430
|
let byAuthority = new Map<string, { paths: Set<string>; parentPaths: Set<string>; }>();
|
|
427
431
|
for (let { authorityId, paths, parentPaths } of batched) {
|
|
@@ -456,15 +460,23 @@ export class RemoteWatcher {
|
|
|
456
460
|
let config: WatchConfig = {
|
|
457
461
|
paths: Array.from(paths),
|
|
458
462
|
parentPaths: Array.from(parentPaths),
|
|
463
|
+
fullHistory: Querysub.SEND_FULL_HISTORY_ON_INITIAL_SYNC,
|
|
459
464
|
};
|
|
465
|
+
if (isDebugLogEnabled()) {
|
|
466
|
+
for (let path of paths) {
|
|
467
|
+
auditLog("Asking to watch path", { path, authorityId, targetNodeThreadId: debugNodeThread(authorityId) });
|
|
468
|
+
}
|
|
469
|
+
for (let path of parentPaths) {
|
|
470
|
+
auditLog("Asking to watch parent path", { path, authorityId, targetNodeThreadId: debugNodeThread(authorityId) });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
460
473
|
logErrors(RemoteWatcher.REMOTE_WATCH_FUNCTION(config, authorityId));
|
|
461
474
|
}
|
|
462
475
|
});
|
|
463
476
|
}
|
|
464
477
|
);
|
|
465
478
|
|
|
466
|
-
/**
|
|
467
|
-
|
|
479
|
+
/** @deprecated Call pathWatcher.unwatchPath instead. */
|
|
468
480
|
public unwatchLatest(config: WatchConfig) {
|
|
469
481
|
// IMPORTANT! This serial is important, otherwise there is a race condition between starting to unwatch,
|
|
470
482
|
// and unwatching, which can result in marking a path as unwatched when it is watched.
|
|
@@ -564,13 +576,16 @@ export class RemoteWatcher {
|
|
|
564
576
|
let parentWatchObj = this.remoteWatchParents.get(parentPath);
|
|
565
577
|
if (parentWatchObj) {
|
|
566
578
|
for (let [nodeId, obj] of parentWatchObj.nodesId) {
|
|
567
|
-
if (
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
579
|
+
if (parentWatchObj.useFullPathHash) {
|
|
580
|
+
let route = PathRouter.getSingleKeyRoute(path);
|
|
581
|
+
if (obj.start <= route && route < obj.end) {
|
|
582
|
+
return nodeId;
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
let route = PathRouter.getRouteChildKey(path);
|
|
586
|
+
if (obj.start <= route && route < obj.end) {
|
|
587
|
+
return nodeId;
|
|
588
|
+
}
|
|
574
589
|
}
|
|
575
590
|
}
|
|
576
591
|
// NOTE: I think it is a bug if we hit here. There shouldn't be a parent, but have us not match it?
|
|
@@ -582,8 +597,10 @@ export class RemoteWatcher {
|
|
|
582
597
|
return Array.from(new Set(this.remoteWatchPaths.values()));
|
|
583
598
|
}
|
|
584
599
|
|
|
600
|
+
|
|
601
|
+
|
|
585
602
|
public async refreshAllWatches(authorityNodeId: string) {
|
|
586
|
-
await
|
|
603
|
+
await authorityLookup.startSyncing();
|
|
587
604
|
await this.watchUnwatchSerial("refreshAllWatches", async () => {
|
|
588
605
|
// Unwatch all paths, reset all remoteWatchPaths, and then watch all paths again SYNCHRONOUSLY,
|
|
589
606
|
// so there is no time we aren't watching the paths.
|
|
@@ -631,8 +648,8 @@ export class RemoteWatcher {
|
|
|
631
648
|
|
|
632
649
|
export const remoteWatcher = new RemoteWatcher();
|
|
633
650
|
(globalThis as any).remoteWatcher = remoteWatcher;
|
|
634
|
-
|
|
635
|
-
pathWatcher.
|
|
651
|
+
void Promise.resolve().finally(() => {
|
|
652
|
+
pathWatcher.setUnwatchCallback(config => remoteWatcher.unwatchLatest(config));
|
|
653
|
+
pathWatcher.setWatchRemoteCallback(config => remoteWatcher.watchLatest(config));
|
|
636
654
|
authorityStorage.setGetMultiNodesForParent(path => remoteWatcher.getMultiNodesForParent(path));
|
|
637
|
-
pathValueCommitter.setGetRemoteWatchNodeId(path => remoteWatcher.getExistingWatchRemoteNodeId(path));
|
|
638
655
|
});
|