querysub 0.406.0 → 0.408.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/bin/audit-disk-values.js +7 -0
- package/bin/deploy-prefixes.js +7 -0
- package/package.json +5 -3
- package/src/-a-archives/archiveCache.ts +12 -9
- package/src/-a-auth/certs.ts +1 -1
- package/src/-c-identity/IdentityController.ts +9 -1
- package/src/-f-node-discovery/NodeDiscovery.ts +63 -10
- package/src/0-path-value-core/AuthorityLookup.ts +14 -4
- package/src/0-path-value-core/PathRouter.ts +247 -117
- package/src/0-path-value-core/PathRouterRouteOverride.ts +1 -1
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +4 -2
- package/src/0-path-value-core/PathValueCommitter.ts +68 -31
- package/src/0-path-value-core/PathValueController.ts +77 -8
- package/src/0-path-value-core/PathWatcher.ts +46 -4
- package/src/0-path-value-core/ShardPrefixes.ts +6 -0
- package/src/0-path-value-core/ValidStateComputer.ts +20 -8
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +18 -55
- package/src/0-path-value-core/pathValueArchives.ts +19 -8
- package/src/0-path-value-core/pathValueCore.ts +75 -27
- package/src/0-path-value-core/startupAuthority.ts +9 -9
- package/src/1-path-client/RemoteWatcher.ts +217 -178
- package/src/1-path-client/pathValueClientWatcher.ts +6 -11
- package/src/2-proxy/pathValueProxy.ts +2 -3
- package/src/3-path-functions/PathFunctionRunner.ts +3 -1
- package/src/3-path-functions/syncSchema.ts +6 -2
- package/src/4-deploy/deployGetFunctionsInner.ts +1 -1
- package/src/4-deploy/deployPrefixes.ts +14 -0
- package/src/4-deploy/edgeNodes.ts +1 -1
- package/src/4-querysub/Querysub.ts +17 -5
- package/src/4-querysub/QuerysubController.ts +21 -10
- package/src/4-querysub/predictionQueue.tsx +3 -0
- package/src/4-querysub/querysubPrediction.ts +27 -20
- package/src/5-diagnostics/nodeMetadata.ts +17 -0
- package/src/diagnostics/NodeConnectionsPage.tsx +167 -0
- package/src/diagnostics/NodeViewer.tsx +11 -15
- package/src/diagnostics/PathDistributionInfo.tsx +102 -0
- package/src/diagnostics/SyncTestPage.tsx +19 -8
- package/src/diagnostics/auditDiskValues.ts +221 -0
- package/src/diagnostics/auditDiskValuesEntry.ts +43 -0
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +5 -1
- package/src/diagnostics/logs/TimeRangeSelector.tsx +3 -3
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +2 -0
- package/src/diagnostics/managementPages.tsx +10 -1
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +3 -2
- package/src/diagnostics/pathAuditer.ts +21 -0
- package/src/path.ts +9 -2
- package/src/rangeMath.ts +41 -0
- package/tempnotes.txt +5 -58
- package/test.ts +13 -295
- package/src/diagnostics/benchmark.ts +0 -139
- package/src/diagnostics/runSaturationTest.ts +0 -416
- package/src/diagnostics/satSchema.ts +0 -64
- package/src/test/mongoSatTest.tsx +0 -55
- package/src/test/satTest.ts +0 -193
- package/src/test/test.tsx +0 -552
|
@@ -2,24 +2,26 @@
|
|
|
2
2
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
3
3
|
import { batchFunction, runInSerial, runInfinitePoll } from "socket-function/src/batching";
|
|
4
4
|
import { cache } from "socket-function/src/caching";
|
|
5
|
-
import { isNode } from "socket-function/src/misc";
|
|
5
|
+
import { isNode, sort } from "socket-function/src/misc";
|
|
6
6
|
import { measureFnc, measureBlock } from "socket-function/src/profiling/measure";
|
|
7
7
|
import { isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
8
8
|
import { PathValueController } from "../0-path-value-core/PathValueController";
|
|
9
|
-
import { PathRouter } from "../0-path-value-core/PathRouter";
|
|
9
|
+
import { AuthoritySpec, PathRouter } from "../0-path-value-core/PathRouter";
|
|
10
10
|
import { WatchConfig, authorityStorage } from "../0-path-value-core/pathValueCore";
|
|
11
11
|
import { pathWatcher } from "../0-path-value-core/PathWatcher";
|
|
12
12
|
import { ActionsHistory } from "../diagnostics/ActionsHistory";
|
|
13
13
|
import { registerResource } from "../diagnostics/trackResources";
|
|
14
14
|
import { logErrors } from "../errors";
|
|
15
|
-
import { green, yellow } from "socket-function/src/formatting/logColors";
|
|
16
|
-
import { getParentPathStr } from "../path";
|
|
15
|
+
import { blue, green, yellow } from "socket-function/src/formatting/logColors";
|
|
16
|
+
import { getParentPathStr, hack_stripPackedPath } from "../path";
|
|
17
17
|
import { isEmpty } from "../misc";
|
|
18
18
|
import { isDevDebugbreak } from "../config";
|
|
19
19
|
import { auditLog, isDebugLogEnabled } from "../0-path-value-core/auditLogs";
|
|
20
20
|
import { debugNodeThread } from "../-c-identity/IdentityController";
|
|
21
21
|
import { authorityLookup } from "../0-path-value-core/AuthorityLookup";
|
|
22
|
-
import { decodeParentFilter, encodeParentFilter } from "../0-path-value-core/hackedPackedPathParentFiltering";
|
|
22
|
+
import { decodeParentFilter, encodeParentFilter, registerGetSpecForChildPath } from "../0-path-value-core/hackedPackedPathParentFiltering";
|
|
23
|
+
import { removeRange, rangesOverlap } from "../rangeMath";
|
|
24
|
+
|
|
23
25
|
setImmediate(() => import("../4-querysub/Querysub"));
|
|
24
26
|
|
|
25
27
|
// 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.
|
|
@@ -35,19 +37,25 @@ export class RemoteWatcher {
|
|
|
35
37
|
// path => nodeId
|
|
36
38
|
// isOwnNodeId(nodeId) means we are watching it locally
|
|
37
39
|
private remoteWatchPaths = registerResource("paths|remoteWatchPaths", new Map<string, string>());
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
nodesId: Map<string, {
|
|
40
|
+
|
|
41
|
+
// cleanPath (hack_stripPackedPath(path)) =>
|
|
42
|
+
private remoteWatchParents2 = registerResource("paths|remoteWatchParents", new Map<string, {
|
|
43
|
+
ranges: {
|
|
44
|
+
// inclusive
|
|
44
45
|
start: number;
|
|
46
|
+
// exclusive
|
|
45
47
|
end: number;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// encodeParentFilter({ path: cleanPath, startFraction: start, endFraction: end })
|
|
49
|
+
finalPath: string;
|
|
50
|
+
// All of the requests paths that request it (pre decodeParentFilter).
|
|
51
|
+
inputPaths: Set<string>;
|
|
52
|
+
|
|
53
|
+
// The authorityId satisifying this request
|
|
54
|
+
authorityId: string;
|
|
55
|
+
authoritySpec: AuthoritySpec;
|
|
56
|
+
|
|
57
|
+
suppressedWatches: Set<string>;
|
|
58
|
+
}[];
|
|
51
59
|
}>());
|
|
52
60
|
|
|
53
61
|
private disconnectedPaths = registerResource("paths|disconnectedPaths", new Set<string>());
|
|
@@ -69,22 +77,27 @@ export class RemoteWatcher {
|
|
|
69
77
|
}
|
|
70
78
|
}
|
|
71
79
|
let parentPaths: string[] = [];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
|
|
81
|
+
// Remove all ranges from remoteWatchParents2 that matches this, adding all inputPaths to parentPaths so we can try to rewatch them again
|
|
82
|
+
for (let [cleanPath, watchObj] of this.remoteWatchParents2.entries()) {
|
|
83
|
+
let removedRanges = watchObj.ranges.filter(x => x.authorityId === authorityId);
|
|
84
|
+
watchObj.ranges = watchObj.ranges.filter(x => x.authorityId !== authorityId);
|
|
85
|
+
if (watchObj.ranges.length === 0) {
|
|
86
|
+
this.remoteWatchParents2.delete(cleanPath);
|
|
87
|
+
}
|
|
88
|
+
for (let range of removedRanges) {
|
|
89
|
+
for (let inputPath of range.inputPaths) {
|
|
90
|
+
parentPaths.push(inputPath);
|
|
77
91
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
parentPaths.push(path);
|
|
92
|
+
for (let suppressedPath of range.suppressedWatches) {
|
|
93
|
+
if (!this.remoteWatchPaths.has(suppressedPath)) continue;
|
|
94
|
+
// NOTE: We shouldn't need to remove the remote watch path, as it being suppressed means it shouldn't be watched. However, I guess we can because we definitely don't want to double-watch it.
|
|
95
|
+
this.remoteWatchPaths.delete(suppressedPath);
|
|
96
|
+
paths.push(suppressedPath);
|
|
85
97
|
}
|
|
86
98
|
}
|
|
87
99
|
}
|
|
100
|
+
|
|
88
101
|
if (paths.length === 0 && parentPaths.length === 0) return;
|
|
89
102
|
|
|
90
103
|
for (let path of paths) {
|
|
@@ -120,14 +133,23 @@ export class RemoteWatcher {
|
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
|
|
136
|
+
public getRemoteWatchParentRange(path: string) {
|
|
137
|
+
let parentPath = getParentPathStr(path);
|
|
138
|
+
let watchObj = this.remoteWatchParents2.get(parentPath);
|
|
139
|
+
if (!watchObj) return undefined;
|
|
140
|
+
// NOTE: PathRouter.getChildReadNodes PROMISES That all of the nodes will hash in the same way, and if they all hash in the same way, and they have different ranges, then every path will uniquely map to a single value.
|
|
141
|
+
return watchObj.ranges.find(x => {
|
|
142
|
+
let route = PathRouter.getRouteFull({ path, spec: x.authoritySpec });
|
|
143
|
+
return x.start <= route && route < x.end;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
123
148
|
public debugIsWatchingPath(path: string) {
|
|
124
149
|
// HACK: If there is no read node... pretend we are watching it. Because... for now, this is a known
|
|
125
150
|
// and the code calling this function isn't looking for that specific issue.
|
|
126
151
|
return this.remoteWatchPaths.has(path) || !PathRouter.getReadyAuthority(path);
|
|
127
152
|
}
|
|
128
|
-
public debugIsWatchingParentPath(path: string) {
|
|
129
|
-
return this.remoteWatchParents.has(path) || !PathRouter.getReadyAuthority(path);
|
|
130
|
-
}
|
|
131
153
|
|
|
132
154
|
/** @deprecated If you want to watch something, call pathWatcher.watchPath instead. */
|
|
133
155
|
public watchLatest(config: WatchConfig & { debugName?: string }) {
|
|
@@ -149,10 +171,16 @@ export class RemoteWatcher {
|
|
|
149
171
|
return this.watchLatestBase(config);
|
|
150
172
|
}
|
|
151
173
|
|
|
152
|
-
|
|
153
|
-
public
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
// As in, is this the path that we're actually watching on the remote? We remap all of the requested paths to the final paths. So, this won't be something that the client application code would understand. However, this would be what the remote server thinks we're intending to watch.
|
|
175
|
+
public isFinalRemoteWatchPath(config: {
|
|
176
|
+
parentPath: string;
|
|
177
|
+
nodeId: string;
|
|
178
|
+
}) {
|
|
179
|
+
let { parentPath, nodeId } = config;
|
|
180
|
+
let cleanPath = hack_stripPackedPath(parentPath);
|
|
181
|
+
let watchObj = this.remoteWatchParents2.get(cleanPath);
|
|
182
|
+
if (!watchObj) return false;
|
|
183
|
+
return watchObj.ranges.some(x => x.authorityId === nodeId && x.finalPath === parentPath);
|
|
156
184
|
}
|
|
157
185
|
|
|
158
186
|
private watchUnwatchSerial = runInSerial(async (name: string, fnc: () => Promise<unknown>) => {
|
|
@@ -176,7 +204,6 @@ export class RemoteWatcher {
|
|
|
176
204
|
forceWatch?: boolean;
|
|
177
205
|
}) {
|
|
178
206
|
let watchesPerAuthority = new Map<string, { paths: string[]; parentPaths: string[] }>();
|
|
179
|
-
|
|
180
207
|
let unwatchParentsPerAuthority = new Map<string, Set<string>>();
|
|
181
208
|
|
|
182
209
|
let newDisconnectPaths = 0;
|
|
@@ -236,128 +263,113 @@ export class RemoteWatcher {
|
|
|
236
263
|
paths.paths.push(path);
|
|
237
264
|
}
|
|
238
265
|
|
|
239
|
-
for (let
|
|
266
|
+
for (let path of config.parentPaths) {
|
|
240
267
|
// NOTE: We weren't ignoring local paths here for a while, but... I'm pretty sure we should, to save time.
|
|
241
|
-
if (PathRouter.isLocalPath(
|
|
242
|
-
let watchObj = this.remoteWatchParents.get(originalPath);
|
|
243
|
-
if (
|
|
244
|
-
watchObj
|
|
245
|
-
&& watchObj.nodesId.size === watchObj.fullNodeCount
|
|
246
|
-
&& !config.tryReconnect
|
|
247
|
-
) continue;
|
|
248
|
-
|
|
249
|
-
let { useFullPathHash, nodes } = PathRouter.getChildReadNodes(originalPath, {
|
|
250
|
-
// Pass existing connected node ids as preferred
|
|
251
|
-
preferredNodeIds: watchObj && Array.from(watchObj.nodesId.keys())
|
|
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
|
-
}
|
|
268
|
+
if (PathRouter.isLocalPath(path)) continue;
|
|
264
269
|
|
|
265
|
-
//
|
|
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
|
-
}
|
|
270
|
+
// IMPORTANT! We HAVE to watch only the range we want, as... if we happen to watch more than the node has, it'll try to get it from other nodes, which is definitely not what we want.
|
|
275
271
|
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
let decodedParentFilter = decodeParentFilter(path);
|
|
273
|
+
let cleanPath = decodedParentFilter?.path ?? path;
|
|
278
274
|
|
|
279
275
|
let rangeStart = 0;
|
|
280
276
|
let rangeEnd = 1;
|
|
281
|
-
|
|
282
|
-
let nodesToAdd = new Map<string, { start: number; end: number }>();
|
|
283
277
|
if (decodedParentFilter) {
|
|
284
278
|
rangeStart = decodedParentFilter.start;
|
|
285
279
|
rangeEnd = decodedParentFilter.end;
|
|
286
280
|
}
|
|
287
281
|
|
|
288
|
-
|
|
289
|
-
let curStart = 0;
|
|
290
|
-
for (let node of nodes) {
|
|
291
|
-
let r = node.range;
|
|
292
|
-
if (r.start > curStart) {
|
|
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.`);
|
|
294
|
-
curStart = r.start;
|
|
295
|
-
}
|
|
296
|
-
let nextEnd = Math.min(r.end, rangeEnd);
|
|
297
|
-
// Ignore values with no overlap (mostly trailing values)
|
|
298
|
-
if (nextEnd <= curStart) continue;
|
|
282
|
+
let watchObj = this.remoteWatchParents2.get(cleanPath);
|
|
299
283
|
|
|
300
|
-
|
|
301
|
-
|
|
284
|
+
if (watchObj && !config.tryReconnect) {
|
|
285
|
+
// If all the ranges we want are already covered, then we can just use them
|
|
286
|
+
let requiredRanges: { start: number; end: number }[] = [{
|
|
287
|
+
start: rangeStart,
|
|
288
|
+
end: rangeEnd,
|
|
289
|
+
}];
|
|
290
|
+
for (let range of watchObj.ranges) {
|
|
291
|
+
removeRange(requiredRanges, range);
|
|
302
292
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (node.range.start > lastValue) {
|
|
310
|
-
fullRangeCovered = false;
|
|
311
|
-
break;
|
|
293
|
+
if (requiredRanges.length === 0) {
|
|
294
|
+
this.disconnectedParents.delete(path);
|
|
295
|
+
for (let range of watchObj.ranges) {
|
|
296
|
+
if (rangesOverlap(range, { start: rangeStart, end: rangeEnd })) {
|
|
297
|
+
range.inputPaths.add(path);
|
|
298
|
+
}
|
|
312
299
|
}
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
if (lastValue < rangeEnd) {
|
|
316
|
-
fullRangeCovered = false;
|
|
300
|
+
continue;
|
|
317
301
|
}
|
|
318
302
|
}
|
|
319
|
-
|
|
320
|
-
|
|
303
|
+
|
|
304
|
+
let { nodes } = PathRouter.getChildReadNodes(cleanPath, {
|
|
305
|
+
// Pass existing connected node ids as preferred
|
|
306
|
+
preferredNodeIds: watchObj?.ranges.map(x => x.authorityId) || []
|
|
307
|
+
});
|
|
308
|
+
// NOTE: getChildReadNodes either returns a full range, or nothing
|
|
309
|
+
if (nodes.length === 0) {
|
|
310
|
+
if (!this.disconnectedParents.has(path)) {
|
|
321
311
|
newDisconnectParents++;
|
|
322
|
-
this.disconnectedParents.add(
|
|
312
|
+
this.disconnectedParents.add(path);
|
|
323
313
|
}
|
|
324
|
-
|
|
325
|
-
this.disconnectedParents.delete(originalPath);
|
|
326
|
-
foundParentPaths++;
|
|
314
|
+
continue;
|
|
327
315
|
}
|
|
316
|
+
this.disconnectedParents.delete(path);
|
|
317
|
+
foundParentPaths++;
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
// NOTE: If the previous call to get childrenodes returns something entirely different, that's fine. If there's a disconnect, we would have removed the ranges already, so they're probably still valid. And we passed them as preferred node IDs, so they'll probably be chosen again. But if they weren't, it's fine. We can just add new ranges for any that we happen to be missing.
|
|
321
|
+
// - As in, we don't need to find the difference between the previous time, we just need to find what new watches do we need to add.
|
|
328
322
|
|
|
329
|
-
// Unwatch nodes that have changed
|
|
330
|
-
if (watchObj) {
|
|
331
|
-
for (let nodeId of watchObj.nodesId.keys()) {
|
|
332
|
-
if (!nodesToAdd.has(nodeId)) {
|
|
333
|
-
let parentPaths = unwatchParentsPerAuthority.get(nodeId);
|
|
334
|
-
if (!parentPaths) {
|
|
335
|
-
parentPaths = new Set();
|
|
336
|
-
unwatchParentsPerAuthority.set(nodeId, parentPaths);
|
|
337
|
-
}
|
|
338
|
-
parentPaths.add(watchedPath);
|
|
339
|
-
watchObj.nodesId.delete(nodeId);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
323
|
|
|
344
324
|
if (!watchObj) {
|
|
345
|
-
watchObj = {
|
|
346
|
-
this.
|
|
325
|
+
watchObj = { ranges: [] };
|
|
326
|
+
this.remoteWatchParents2.set(cleanPath, watchObj);
|
|
347
327
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
328
|
+
|
|
329
|
+
// Compute which parts of [rangeStart, rangeEnd] aren't yet covered by existing watches
|
|
330
|
+
let missingRanges: { start: number; end: number }[] = [{ start: rangeStart, end: rangeEnd }];
|
|
331
|
+
for (let range of watchObj.ranges) {
|
|
332
|
+
removeRange(missingRanges, range);
|
|
351
333
|
}
|
|
352
334
|
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
335
|
+
// For each missing sub-range, clip each node's range to exactly that sub-range and add it.
|
|
336
|
+
// NOTE: Nodes has no internal overlaps, and missing ranges by definition are only values that we haven't added (and itself has no overlaps). And so adding the intersection of those will also have no overlaps and only add values that we haven't added.
|
|
337
|
+
for (let missing of missingRanges) {
|
|
338
|
+
for (let node of nodes) {
|
|
339
|
+
if (!rangesOverlap(node.range, missing)) continue;
|
|
340
|
+
|
|
341
|
+
let clippedStart = Math.max(node.range.start, missing.start);
|
|
342
|
+
let clippedEnd = Math.min(node.range.end, missing.end);
|
|
343
|
+
|
|
344
|
+
let finalPath = encodeParentFilter({ path: cleanPath, startFraction: clippedStart, endFraction: clippedEnd });
|
|
345
|
+
let existingRange = watchObj.ranges.find(x => x.finalPath === finalPath && x.authorityId === node.nodeId);
|
|
346
|
+
if (!existingRange) {
|
|
347
|
+
existingRange = {
|
|
348
|
+
start: clippedStart,
|
|
349
|
+
end: clippedEnd,
|
|
350
|
+
finalPath,
|
|
351
|
+
inputPaths: new Set(),
|
|
352
|
+
authorityId: node.nodeId,
|
|
353
|
+
suppressedWatches: new Set(),
|
|
354
|
+
authoritySpec: node.authoritySpec,
|
|
355
|
+
};
|
|
356
|
+
watchObj.ranges.push(existingRange);
|
|
357
|
+
let watchesPer = watchesPerAuthority.get(node.nodeId);
|
|
358
|
+
if (!watchesPer) {
|
|
359
|
+
watchesPer = { paths: [], parentPaths: [] };
|
|
360
|
+
watchesPerAuthority.set(node.nodeId, watchesPer);
|
|
361
|
+
}
|
|
362
|
+
watchesPer.parentPaths.push(finalPath);
|
|
363
|
+
}
|
|
364
|
+
existingRange.inputPaths.add(path);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Register path on pre-existing ranges that already cover parts of [rangeStart, rangeEnd]
|
|
369
|
+
for (let range of watchObj.ranges) {
|
|
370
|
+
if (rangesOverlap(range, { start: rangeStart, end: rangeEnd })) {
|
|
371
|
+
range.inputPaths.add(path);
|
|
359
372
|
}
|
|
360
|
-
paths.parentPaths.push(watchedPath);
|
|
361
373
|
}
|
|
362
374
|
}
|
|
363
375
|
}, `watchLatest|getSingleReadNode`);
|
|
@@ -395,13 +407,15 @@ export class RemoteWatcher {
|
|
|
395
407
|
}
|
|
396
408
|
|
|
397
409
|
if (STOP_KEYS_DOUBLE_SENDS) {
|
|
410
|
+
// NOTE: We could already be watching the value watches, in which case watching the parent watches will cause us to double watch them. This is fine. We don't need to remove all double watches. We just want to remove some.
|
|
398
411
|
paths = paths.filter(path => {
|
|
399
|
-
let
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
412
|
+
let range = this.getRemoteWatchParentRange(path);
|
|
413
|
+
if (range) {
|
|
414
|
+
range.suppressedWatches.add(path);
|
|
415
|
+
auditLog("remoteWatcher outer WATCH SUPPRESSED", { path, remoteNodeId: authorityId });
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
405
419
|
});
|
|
406
420
|
}
|
|
407
421
|
|
|
@@ -428,6 +442,7 @@ export class RemoteWatcher {
|
|
|
428
442
|
const { Querysub } = await import("../4-querysub/Querysub");
|
|
429
443
|
await this.watchUnwatchSerial("watchLatestRemote", async () => {
|
|
430
444
|
let byAuthority = new Map<string, { paths: Set<string>; parentPaths: Set<string>; }>();
|
|
445
|
+
// Collect them and also verify that we're still watching the same authority as we were when we started the batched call
|
|
431
446
|
for (let { authorityId, paths, parentPaths } of batched) {
|
|
432
447
|
let pathsPerAuthority = byAuthority.get(authorityId);
|
|
433
448
|
if (!pathsPerAuthority) {
|
|
@@ -440,9 +455,10 @@ export class RemoteWatcher {
|
|
|
440
455
|
pathsPerAuthority.paths.add(path);
|
|
441
456
|
}
|
|
442
457
|
for (let path of parentPaths) {
|
|
443
|
-
let
|
|
458
|
+
let cleanPath = hack_stripPackedPath(path);
|
|
459
|
+
let watchObj = this.remoteWatchParents2.get(cleanPath);
|
|
444
460
|
if (!watchObj) continue;
|
|
445
|
-
if (!watchObj.
|
|
461
|
+
if (!watchObj.ranges.some(x => x.finalPath === path && x.authorityId === authorityId)) continue;
|
|
446
462
|
pathsPerAuthority.parentPaths.add(path);
|
|
447
463
|
}
|
|
448
464
|
}
|
|
@@ -470,6 +486,7 @@ export class RemoteWatcher {
|
|
|
470
486
|
auditLog("Asking to watch parent path", { path, authorityId, targetNodeThreadId: debugNodeThread(authorityId) });
|
|
471
487
|
}
|
|
472
488
|
}
|
|
489
|
+
|
|
473
490
|
logErrors(RemoteWatcher.REMOTE_WATCH_FUNCTION(config, authorityId));
|
|
474
491
|
}
|
|
475
492
|
});
|
|
@@ -525,19 +542,27 @@ export class RemoteWatcher {
|
|
|
525
542
|
}
|
|
526
543
|
let suppressedRewatches = new Set<string>();
|
|
527
544
|
for (let path of config.parentPaths) {
|
|
528
|
-
let
|
|
545
|
+
let cleanPath = hack_stripPackedPath(path);
|
|
546
|
+
let watchObj = this.remoteWatchParents2.get(cleanPath);
|
|
529
547
|
if (!watchObj) continue;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
548
|
+
for (let range of watchObj.ranges) {
|
|
549
|
+
range.inputPaths.delete(path);
|
|
550
|
+
}
|
|
551
|
+
let removedRanges = watchObj.ranges.filter(x => x.inputPaths.size === 0);
|
|
552
|
+
watchObj.ranges = watchObj.ranges.filter(x => x.inputPaths.size > 0);
|
|
553
|
+
if (watchObj.ranges.length === 0) {
|
|
554
|
+
this.remoteWatchParents2.delete(cleanPath);
|
|
536
555
|
}
|
|
537
|
-
for (let
|
|
538
|
-
innerParentPathRemove(
|
|
556
|
+
for (let removedRange of removedRanges) {
|
|
557
|
+
innerParentPathRemove(removedRange.finalPath, removedRange.authorityId);
|
|
558
|
+
// Rewatch anything that we suppressed watching (which happens when STOP_KEYS_DOUBLE_SENDS is set)
|
|
559
|
+
for (let suppressedWatch of removedRange.suppressedWatches) {
|
|
560
|
+
// Only if we are still watching it (which most of the time, we won't be)
|
|
561
|
+
if (!this.remoteWatchPaths.has(suppressedWatch)) continue;
|
|
562
|
+
suppressedRewatches.add(suppressedWatch);
|
|
563
|
+
auditLog("remoteWatcher inner SUPPRESSED rewatched", { path: suppressedWatch });
|
|
564
|
+
}
|
|
539
565
|
}
|
|
540
|
-
this.remoteWatchParents.delete(path);
|
|
541
566
|
}
|
|
542
567
|
if (suppressedRewatches.size > 0) {
|
|
543
568
|
this.internalWatchLatest({
|
|
@@ -570,29 +595,22 @@ export class RemoteWatcher {
|
|
|
570
595
|
this.unwatchLatest(config);
|
|
571
596
|
}
|
|
572
597
|
|
|
598
|
+
public async flushWatchers() {
|
|
599
|
+
await this.watchUnwatchSerial("flushWatchers", async () => { });
|
|
600
|
+
}
|
|
601
|
+
|
|
573
602
|
@measureFnc
|
|
574
603
|
public getExistingWatchRemoteNodeId(path: string): string | undefined {
|
|
575
|
-
let
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
for (let [nodeId, obj] of parentWatchObj.nodesId) {
|
|
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
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
// NOTE: I think it is a bug if we hit here. There shouldn't be a parent, but have us not match it?
|
|
592
|
-
// But... I also don't see the harm in checking remoteWatchPaths?
|
|
604
|
+
let parentRange = this.getRemoteWatchParentRange(path);
|
|
605
|
+
if (parentRange) {
|
|
606
|
+
return parentRange.authorityId;
|
|
593
607
|
}
|
|
594
608
|
return this.remoteWatchPaths.get(path);
|
|
595
609
|
}
|
|
610
|
+
public getValueWatchRemoteNodeId(path: string): string | undefined {
|
|
611
|
+
return this.remoteWatchPaths.get(path);
|
|
612
|
+
}
|
|
613
|
+
|
|
596
614
|
public getAllRemoteWatchedNodeIds() {
|
|
597
615
|
return Array.from(new Set(this.remoteWatchPaths.values()));
|
|
598
616
|
}
|
|
@@ -611,7 +629,6 @@ export class RemoteWatcher {
|
|
|
611
629
|
// cases where an authority is in a broken state.
|
|
612
630
|
|
|
613
631
|
let pathsToWatch = new Set<string>();
|
|
614
|
-
let parentPathsToWatch = new Set<string>();
|
|
615
632
|
for (let [path, nodeId] of this.remoteWatchPaths) {
|
|
616
633
|
if (nodeId === authorityNodeId) {
|
|
617
634
|
pathsToWatch.add(path);
|
|
@@ -621,19 +638,30 @@ export class RemoteWatcher {
|
|
|
621
638
|
for (let path of pathsToWatch) {
|
|
622
639
|
this.remoteWatchPaths.delete(path);
|
|
623
640
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
641
|
+
let parentRemotePathsToUnwatch = new Set<string>();
|
|
642
|
+
let parentPathsToRewatch = new Set<string>();
|
|
643
|
+
for (let [cleanPath, watchObj] of this.remoteWatchParents2) {
|
|
644
|
+
let ranges = watchObj.ranges.filter(x => x.authorityId === authorityNodeId);
|
|
645
|
+
watchObj.ranges = watchObj.ranges.filter(x => x.authorityId !== authorityNodeId);
|
|
646
|
+
if (watchObj.ranges.length === 0) {
|
|
647
|
+
this.remoteWatchParents2.delete(cleanPath);
|
|
648
|
+
}
|
|
649
|
+
for (let range of ranges) {
|
|
650
|
+
for (let inputPath of range.inputPaths) {
|
|
651
|
+
parentPathsToRewatch.add(inputPath);
|
|
652
|
+
}
|
|
653
|
+
parentRemotePathsToUnwatch.add(range.finalPath);
|
|
628
654
|
}
|
|
629
655
|
}
|
|
630
656
|
let paths = Array.from(pathsToWatch);
|
|
631
|
-
let parentPaths = Array.from(parentPathsToWatch);
|
|
632
657
|
|
|
633
|
-
logErrors(RemoteWatcher.REMOTE_UNWATCH_FUNCTION({
|
|
658
|
+
logErrors(RemoteWatcher.REMOTE_UNWATCH_FUNCTION({
|
|
659
|
+
paths,
|
|
660
|
+
parentPaths: Array.from(parentRemotePathsToUnwatch)
|
|
661
|
+
}, authorityNodeId));
|
|
634
662
|
this.internalWatchLatest({
|
|
635
663
|
paths: Array.from(pathsToWatch),
|
|
636
|
-
parentPaths: Array.from(
|
|
664
|
+
parentPaths: Array.from(parentPathsToRewatch),
|
|
637
665
|
debugName: "refreshAllWatches",
|
|
638
666
|
});
|
|
639
667
|
});
|
|
@@ -641,15 +669,26 @@ export class RemoteWatcher {
|
|
|
641
669
|
|
|
642
670
|
public getWatchReason(path: string): "direct" | "parent" | undefined {
|
|
643
671
|
if (this.remoteWatchPaths.has(path)) return "direct";
|
|
644
|
-
if (this.
|
|
672
|
+
if (this.remoteWatchParents2.has(getParentPathStr(path))) return "parent";
|
|
645
673
|
return undefined;
|
|
646
674
|
}
|
|
647
675
|
}
|
|
648
676
|
|
|
677
|
+
registerGetSpecForChildPath(path => {
|
|
678
|
+
if (!PathRouter.isSelfAuthority(path)) {
|
|
679
|
+
// Depends on the remote authority for this path
|
|
680
|
+
let remoteWatchRange = remoteWatcher.getRemoteWatchParentRange(path);
|
|
681
|
+
if (remoteWatchRange) return remoteWatchRange.authoritySpec;
|
|
682
|
+
|
|
683
|
+
// NOTE: This fall through usually means that we received a value before we started watching it, which is weird. That really shouldn't happen often. It'll probably break things. We will probably ignore this value thinking it has a different hash than it does.
|
|
684
|
+
console.warn(`We do not own a path, but also aren't watching it on a remote, but... receive the value? This is weird, and will probably break things (we will likely ignore this value / not trigger parent watches for it).`, { path });
|
|
685
|
+
}
|
|
686
|
+
return authorityLookup.getOurSpec();
|
|
687
|
+
});
|
|
688
|
+
|
|
649
689
|
export const remoteWatcher = new RemoteWatcher();
|
|
650
690
|
(globalThis as any).remoteWatcher = remoteWatcher;
|
|
651
691
|
void Promise.resolve().finally(() => {
|
|
652
692
|
pathWatcher.setUnwatchCallback(config => remoteWatcher.unwatchLatest(config));
|
|
653
693
|
pathWatcher.setWatchRemoteCallback(config => remoteWatcher.watchLatest(config));
|
|
654
|
-
authorityStorage.setGetMultiNodesForParent(path => remoteWatcher.getMultiNodesForParent(path));
|
|
655
694
|
});
|
|
@@ -14,29 +14,24 @@
|
|
|
14
14
|
- For knowing when values change
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { binarySearchIndex, isNode, recursiveFreeze, sort } from "socket-function/src/misc";
|
|
17
|
+
import { lazy } from "socket-function/src/caching";
|
|
18
|
+
import { isNode } from "socket-function/src/misc";
|
|
20
19
|
import { logErrors } from "../errors";
|
|
21
20
|
import { getParentPathStr, getPathFromStr, hack_stripPackedPath } from "../path";
|
|
22
21
|
import { measureBlock, measureFnc } from "socket-function/src/profiling/measure";
|
|
23
|
-
import { pathValueCommitter
|
|
22
|
+
import { pathValueCommitter } from "../0-path-value-core/PathValueController";
|
|
24
23
|
import { PathValue, Value, getNextTime, Time, ReadLock, MAX_CHANGE_AGE, authorityStorage, getCreatorId, WatchConfig } from "../0-path-value-core/pathValueCore";
|
|
25
24
|
import { pathWatcher } from "../0-path-value-core/PathWatcher";
|
|
26
25
|
import { blue, green, red } from "socket-function/src/formatting/logColors";
|
|
27
|
-
import {
|
|
28
|
-
import { batchFunction, batchFunctionNone, runInfinitePoll } from "socket-function/src/batching";
|
|
26
|
+
import { runInfinitePoll } from "socket-function/src/batching";
|
|
29
27
|
import { registerResource } from "../diagnostics/trackResources";
|
|
30
|
-
import
|
|
31
|
-
import { ActionsHistory } from "../diagnostics/ActionsHistory";
|
|
32
|
-
import { getOwnNodeId, isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
28
|
+
import { getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
33
29
|
import { PromiseObj } from "../promise";
|
|
34
30
|
import { getOwnMachineId } from "../-a-auth/certs";
|
|
35
31
|
import { remoteWatcher } from "./RemoteWatcher";
|
|
36
32
|
import { heapTagObj } from "../diagnostics/heapTag";
|
|
37
33
|
import { isDevDebugbreak } from "../config";
|
|
38
|
-
import {
|
|
39
|
-
import { decodeParentFilter, matchesParentRangeFilter } from "../0-path-value-core/hackedPackedPathParentFiltering";
|
|
34
|
+
import { matchesParentRangeFilter } from "../0-path-value-core/hackedPackedPathParentFiltering";
|
|
40
35
|
|
|
41
36
|
const pathValueCtor = heapTagObj("PathValue");
|
|
42
37
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { runInfinitePoll } from "socket-function/src/batching";
|
|
2
2
|
import { cache, lazy } from "socket-function/src/caching";
|
|
3
3
|
import { appendToPathStr, rootPathStr } from "../path";
|
|
4
|
-
import { MAX_CHANGE_AGE } from "../0-path-value-core/pathValueCore";
|
|
5
4
|
import { runCodeWithDatabase } from "./pathDatabaseProxyBase";
|
|
6
5
|
import { canHaveChildren } from "socket-function/src/types";
|
|
7
|
-
import { isNode } from "socket-function/src/misc";
|
|
6
|
+
import { isNode, timeInMinute } from "socket-function/src/misc";
|
|
8
7
|
|
|
9
8
|
let noopProxy = createPathValueProxy({ getCallback: () => undefined, setCallback: () => { }, getSymbol: () => undefined, getKeys: () => [] });
|
|
10
9
|
let gettingPath: { value: string } | undefined;
|
|
@@ -67,7 +66,7 @@ export function createPathValueProxy(
|
|
|
67
66
|
let createdCount = 0;
|
|
68
67
|
let createProxyCached = cache(createProxy);
|
|
69
68
|
let proxyClearLoop = lazy(() => {
|
|
70
|
-
runInfinitePoll(
|
|
69
|
+
runInfinitePoll(timeInMinute * 5, function clearProxyCache() {
|
|
71
70
|
// TODO: Maybe allow proxies to stick around?
|
|
72
71
|
//if (createdCount > 1000 * 100)
|
|
73
72
|
{
|
|
@@ -37,8 +37,10 @@ export const functionSchema = rawSchema<{
|
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
39
|
}>();
|
|
40
|
-
|
|
40
|
+
// NOTE: Looks like set immediate will run after even dynamic imports. So this should actually run as late as possible. If not, we might need to make this so the code that's looking for prefixes explicitly asks for the dynamic prefixes when it's ready.
|
|
41
|
+
setImmediate(() => {
|
|
41
42
|
for (let moduleId of getAllDevelopmentModulesIds()) {
|
|
43
|
+
addRoutingPrefixForDeploy(getProxyPath(() => functionSchema()[getDomain()].PathFunctionRunner[moduleId].Sources));
|
|
42
44
|
addRoutingPrefixForDeploy(getProxyPath(() => functionSchema()[getDomain()].PathFunctionRunner[moduleId].Calls));
|
|
43
45
|
addRoutingPrefixForDeploy(getProxyPath(() => functionSchema()[getDomain()].PathFunctionRunner[moduleId].Results));
|
|
44
46
|
}
|