querysub 0.405.0 → 0.407.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/deploy-prefixes.js +7 -0
- package/package.json +2 -1
- package/src/-f-node-discovery/NodeDiscovery.ts +0 -2
- package/src/0-path-value-core/AuthorityLookup.ts +7 -2
- package/src/0-path-value-core/PathRouter.ts +170 -84
- package/src/0-path-value-core/PathRouterRouteOverride.ts +2 -2
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +2 -8
- package/src/0-path-value-core/PathValueCommitter.ts +65 -30
- package/src/0-path-value-core/PathValueController.ts +2 -4
- package/src/0-path-value-core/PathWatcher.ts +7 -4
- package/src/0-path-value-core/ShardPrefixes.ts +4 -0
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +12 -31
- package/src/0-path-value-core/pathValueArchives.ts +3 -3
- package/src/0-path-value-core/pathValueCore.ts +32 -24
- package/src/0-path-value-core/startupAuthority.ts +9 -9
- package/src/1-path-client/RemoteWatcher.ts +188 -170
- package/src/1-path-client/pathValueClientWatcher.ts +6 -11
- package/src/2-proxy/garbageCollection.ts +4 -3
- 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/diagnostics/SyncTestPage.tsx +19 -8
- package/src/functional/promiseCache.ts +5 -2
- package/src/path.ts +15 -2
- package/src/rangeMath.ts +41 -0
- package/tempnotes.txt +12 -26
- package/test.ts +8 -4
- package/src/diagnostics/logs/BrowserLargeFileCache.ts +0 -64
|
@@ -2,7 +2,7 @@
|
|
|
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";
|
|
@@ -12,14 +12,15 @@ 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
22
|
import { decodeParentFilter, encodeParentFilter } from "../0-path-value-core/hackedPackedPathParentFiltering";
|
|
23
|
+
import { removeRange, rangesOverlap } from "../rangeMath";
|
|
23
24
|
setImmediate(() => import("../4-querysub/Querysub"));
|
|
24
25
|
|
|
25
26
|
// 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 +36,25 @@ export class RemoteWatcher {
|
|
|
35
36
|
// path => nodeId
|
|
36
37
|
// isOwnNodeId(nodeId) means we are watching it locally
|
|
37
38
|
private remoteWatchPaths = registerResource("paths|remoteWatchPaths", new Map<string, string>());
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
nodesId: Map<string, {
|
|
39
|
+
|
|
40
|
+
// cleanPath (hack_stripPackedPath(path)) =>
|
|
41
|
+
private remoteWatchParents2 = registerResource("paths|remoteWatchParents", new Map<string, {
|
|
42
|
+
ranges: {
|
|
43
|
+
// inclusive
|
|
44
44
|
start: number;
|
|
45
|
+
// exclusive
|
|
45
46
|
end: number;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
// encodeParentFilter({ path: cleanPath, startFraction: start, endFraction: end })
|
|
48
|
+
finalPath: string;
|
|
49
|
+
// All of the requests paths that request it (pre decodeParentFilter).
|
|
50
|
+
inputPaths: Set<string>;
|
|
51
|
+
|
|
52
|
+
// The authorityId satisifying this request
|
|
53
|
+
authorityId: string;
|
|
54
|
+
|
|
55
|
+
// All the paths that have getParentPathStr(path) === cleanPath, and PathRouter.getRouteChildKey is within start and end. We suppress these, as our parent watch implicitly watches them. HOWEVER, We have to track them So we can re-watch them if the parent watch is removed.
|
|
56
|
+
suppressedWatches: Set<string>;
|
|
57
|
+
}[];
|
|
51
58
|
}>());
|
|
52
59
|
|
|
53
60
|
private disconnectedPaths = registerResource("paths|disconnectedPaths", new Set<string>());
|
|
@@ -69,22 +76,27 @@ export class RemoteWatcher {
|
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
78
|
let parentPaths: string[] = [];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
|
|
80
|
+
// Remove all ranges from remoteWatchParents2 that matches this, adding all inputPaths to parentPaths so we can try to rewatch them again
|
|
81
|
+
for (let [cleanPath, watchObj] of this.remoteWatchParents2.entries()) {
|
|
82
|
+
let removedRanges = watchObj.ranges.filter(x => x.authorityId === authorityId);
|
|
83
|
+
watchObj.ranges = watchObj.ranges.filter(x => x.authorityId !== authorityId);
|
|
84
|
+
if (watchObj.ranges.length === 0) {
|
|
85
|
+
this.remoteWatchParents2.delete(cleanPath);
|
|
86
|
+
}
|
|
87
|
+
for (let range of removedRanges) {
|
|
88
|
+
for (let inputPath of range.inputPaths) {
|
|
89
|
+
parentPaths.push(inputPath);
|
|
77
90
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
parentPaths.push(path);
|
|
91
|
+
for (let suppressedPath of range.suppressedWatches) {
|
|
92
|
+
if (!this.remoteWatchPaths.has(suppressedPath)) continue;
|
|
93
|
+
// 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.
|
|
94
|
+
this.remoteWatchPaths.delete(suppressedPath);
|
|
95
|
+
paths.push(suppressedPath);
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
}
|
|
99
|
+
|
|
88
100
|
if (paths.length === 0 && parentPaths.length === 0) return;
|
|
89
101
|
|
|
90
102
|
for (let path of paths) {
|
|
@@ -125,9 +137,6 @@ export class RemoteWatcher {
|
|
|
125
137
|
// and the code calling this function isn't looking for that specific issue.
|
|
126
138
|
return this.remoteWatchPaths.has(path) || !PathRouter.getReadyAuthority(path);
|
|
127
139
|
}
|
|
128
|
-
public debugIsWatchingParentPath(path: string) {
|
|
129
|
-
return this.remoteWatchParents.has(path) || !PathRouter.getReadyAuthority(path);
|
|
130
|
-
}
|
|
131
140
|
|
|
132
141
|
/** @deprecated If you want to watch something, call pathWatcher.watchPath instead. */
|
|
133
142
|
public watchLatest(config: WatchConfig & { debugName?: string }) {
|
|
@@ -149,10 +158,16 @@ export class RemoteWatcher {
|
|
|
149
158
|
return this.watchLatestBase(config);
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
|
|
153
|
-
public
|
|
154
|
-
|
|
155
|
-
|
|
161
|
+
// 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.
|
|
162
|
+
public isFinalRemoteWatchPath(config: {
|
|
163
|
+
parentPath: string;
|
|
164
|
+
nodeId: string;
|
|
165
|
+
}) {
|
|
166
|
+
let { parentPath, nodeId } = config;
|
|
167
|
+
let cleanPath = hack_stripPackedPath(parentPath);
|
|
168
|
+
let watchObj = this.remoteWatchParents2.get(cleanPath);
|
|
169
|
+
if (!watchObj) return false;
|
|
170
|
+
return watchObj.ranges.some(x => x.authorityId === nodeId && x.finalPath === parentPath);
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
private watchUnwatchSerial = runInSerial(async (name: string, fnc: () => Promise<unknown>) => {
|
|
@@ -176,7 +191,6 @@ export class RemoteWatcher {
|
|
|
176
191
|
forceWatch?: boolean;
|
|
177
192
|
}) {
|
|
178
193
|
let watchesPerAuthority = new Map<string, { paths: string[]; parentPaths: string[] }>();
|
|
179
|
-
|
|
180
194
|
let unwatchParentsPerAuthority = new Map<string, Set<string>>();
|
|
181
195
|
|
|
182
196
|
let newDisconnectPaths = 0;
|
|
@@ -236,128 +250,111 @@ export class RemoteWatcher {
|
|
|
236
250
|
paths.paths.push(path);
|
|
237
251
|
}
|
|
238
252
|
|
|
239
|
-
for (let
|
|
253
|
+
for (let path of config.parentPaths) {
|
|
240
254
|
// 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
|
-
}
|
|
255
|
+
if (PathRouter.isLocalPath(path)) continue;
|
|
264
256
|
|
|
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
|
-
}
|
|
257
|
+
// 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
258
|
|
|
276
|
-
|
|
277
|
-
|
|
259
|
+
let decodedParentFilter = decodeParentFilter(path);
|
|
260
|
+
let cleanPath = decodedParentFilter?.path ?? path;
|
|
278
261
|
|
|
279
262
|
let rangeStart = 0;
|
|
280
263
|
let rangeEnd = 1;
|
|
281
|
-
|
|
282
|
-
let nodesToAdd = new Map<string, { start: number; end: number }>();
|
|
283
264
|
if (decodedParentFilter) {
|
|
284
265
|
rangeStart = decodedParentFilter.start;
|
|
285
266
|
rangeEnd = decodedParentFilter.end;
|
|
286
267
|
}
|
|
287
268
|
|
|
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;
|
|
269
|
+
let watchObj = this.remoteWatchParents2.get(cleanPath);
|
|
299
270
|
|
|
300
|
-
|
|
301
|
-
|
|
271
|
+
if (watchObj && !config.tryReconnect) {
|
|
272
|
+
// If all the ranges we want are already covered, then we can just use them
|
|
273
|
+
let requiredRanges: { start: number; end: number }[] = [{
|
|
274
|
+
start: rangeStart,
|
|
275
|
+
end: rangeEnd,
|
|
276
|
+
}];
|
|
277
|
+
for (let range of watchObj.ranges) {
|
|
278
|
+
removeRange(requiredRanges, range);
|
|
302
279
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (node.range.start > lastValue) {
|
|
310
|
-
fullRangeCovered = false;
|
|
311
|
-
break;
|
|
280
|
+
if (requiredRanges.length === 0) {
|
|
281
|
+
this.disconnectedParents.delete(path);
|
|
282
|
+
for (let range of watchObj.ranges) {
|
|
283
|
+
if (rangesOverlap(range, { start: rangeStart, end: rangeEnd })) {
|
|
284
|
+
range.inputPaths.add(path);
|
|
285
|
+
}
|
|
312
286
|
}
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
if (lastValue < rangeEnd) {
|
|
316
|
-
fullRangeCovered = false;
|
|
287
|
+
continue;
|
|
317
288
|
}
|
|
289
|
+
|
|
318
290
|
}
|
|
319
|
-
|
|
320
|
-
|
|
291
|
+
|
|
292
|
+
let { nodes } = PathRouter.getChildReadNodes(cleanPath, {
|
|
293
|
+
// Pass existing connected node ids as preferred
|
|
294
|
+
preferredNodeIds: watchObj?.ranges.map(x => x.authorityId) || []
|
|
295
|
+
});
|
|
296
|
+
// NOTE: getChildReadNodes either returns a full range, or nothing
|
|
297
|
+
if (nodes.length === 0) {
|
|
298
|
+
if (!this.disconnectedParents.has(path)) {
|
|
321
299
|
newDisconnectParents++;
|
|
322
|
-
this.disconnectedParents.add(
|
|
300
|
+
this.disconnectedParents.add(path);
|
|
323
301
|
}
|
|
324
|
-
|
|
325
|
-
this.disconnectedParents.delete(originalPath);
|
|
326
|
-
foundParentPaths++;
|
|
302
|
+
continue;
|
|
327
303
|
}
|
|
304
|
+
this.disconnectedParents.delete(path);
|
|
305
|
+
foundParentPaths++;
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
// 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.
|
|
309
|
+
// - 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
310
|
|
|
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
311
|
|
|
344
312
|
if (!watchObj) {
|
|
345
|
-
watchObj = {
|
|
346
|
-
this.
|
|
313
|
+
watchObj = { ranges: [] };
|
|
314
|
+
this.remoteWatchParents2.set(cleanPath, watchObj);
|
|
347
315
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
316
|
+
|
|
317
|
+
// Compute which parts of [rangeStart, rangeEnd] aren't yet covered by existing watches
|
|
318
|
+
let missingRanges: { start: number; end: number }[] = [{ start: rangeStart, end: rangeEnd }];
|
|
319
|
+
for (let range of watchObj.ranges) {
|
|
320
|
+
removeRange(missingRanges, range);
|
|
351
321
|
}
|
|
352
322
|
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
323
|
+
// For each missing sub-range, clip each node's range to exactly that sub-range and add it.
|
|
324
|
+
// 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.
|
|
325
|
+
for (let missing of missingRanges) {
|
|
326
|
+
for (let node of nodes) {
|
|
327
|
+
let clippedStart = Math.max(node.range.start, missing.start);
|
|
328
|
+
let clippedEnd = Math.min(node.range.end, missing.end);
|
|
329
|
+
if (clippedEnd - clippedStart <= 0) continue;
|
|
330
|
+
let finalPath = encodeParentFilter({ path: cleanPath, startFraction: clippedStart, endFraction: clippedEnd });
|
|
331
|
+
let existingRange = watchObj.ranges.find(x => x.finalPath === finalPath && x.authorityId === node.nodeId);
|
|
332
|
+
if (!existingRange) {
|
|
333
|
+
existingRange = {
|
|
334
|
+
start: clippedStart,
|
|
335
|
+
end: clippedEnd,
|
|
336
|
+
finalPath,
|
|
337
|
+
inputPaths: new Set(),
|
|
338
|
+
authorityId: node.nodeId,
|
|
339
|
+
suppressedWatches: new Set(),
|
|
340
|
+
};
|
|
341
|
+
watchObj.ranges.push(existingRange);
|
|
342
|
+
let watchesPer = watchesPerAuthority.get(node.nodeId);
|
|
343
|
+
if (!watchesPer) {
|
|
344
|
+
watchesPer = { paths: [], parentPaths: [] };
|
|
345
|
+
watchesPerAuthority.set(node.nodeId, watchesPer);
|
|
346
|
+
}
|
|
347
|
+
watchesPer.parentPaths.push(finalPath);
|
|
348
|
+
}
|
|
349
|
+
existingRange.inputPaths.add(path);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Register path on pre-existing ranges that already cover parts of [rangeStart, rangeEnd]
|
|
354
|
+
for (let range of watchObj.ranges) {
|
|
355
|
+
if (rangesOverlap(range, { start: rangeStart, end: rangeEnd })) {
|
|
356
|
+
range.inputPaths.add(path);
|
|
359
357
|
}
|
|
360
|
-
paths.parentPaths.push(watchedPath);
|
|
361
358
|
}
|
|
362
359
|
}
|
|
363
360
|
}, `watchLatest|getSingleReadNode`);
|
|
@@ -395,11 +392,16 @@ export class RemoteWatcher {
|
|
|
395
392
|
}
|
|
396
393
|
|
|
397
394
|
if (STOP_KEYS_DOUBLE_SENDS) {
|
|
395
|
+
// 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
396
|
paths = paths.filter(path => {
|
|
399
|
-
let
|
|
400
|
-
let
|
|
401
|
-
if (!
|
|
402
|
-
|
|
397
|
+
let cleanParent = getParentPathStr(path);
|
|
398
|
+
let watchObj = this.remoteWatchParents2.get(cleanParent);
|
|
399
|
+
if (!watchObj) return true;
|
|
400
|
+
let routeKey = PathRouter.getRouteChildKey(path);
|
|
401
|
+
// If we're watching it from any authority, don't watch it now.
|
|
402
|
+
let coveringRange = watchObj.ranges.find(x => x.start <= routeKey && routeKey < x.end);
|
|
403
|
+
if (!coveringRange) return true;
|
|
404
|
+
coveringRange.suppressedWatches.add(path);
|
|
403
405
|
auditLog("remoteWatcher outer WATCH SUPPRESSED", { path, remoteNodeId: authorityId });
|
|
404
406
|
return false;
|
|
405
407
|
});
|
|
@@ -428,6 +430,7 @@ export class RemoteWatcher {
|
|
|
428
430
|
const { Querysub } = await import("../4-querysub/Querysub");
|
|
429
431
|
await this.watchUnwatchSerial("watchLatestRemote", async () => {
|
|
430
432
|
let byAuthority = new Map<string, { paths: Set<string>; parentPaths: Set<string>; }>();
|
|
433
|
+
// Collect them and also verify that we're still watching the same authority as we were when we started the batched call
|
|
431
434
|
for (let { authorityId, paths, parentPaths } of batched) {
|
|
432
435
|
let pathsPerAuthority = byAuthority.get(authorityId);
|
|
433
436
|
if (!pathsPerAuthority) {
|
|
@@ -440,9 +443,10 @@ export class RemoteWatcher {
|
|
|
440
443
|
pathsPerAuthority.paths.add(path);
|
|
441
444
|
}
|
|
442
445
|
for (let path of parentPaths) {
|
|
443
|
-
let
|
|
446
|
+
let cleanPath = hack_stripPackedPath(path);
|
|
447
|
+
let watchObj = this.remoteWatchParents2.get(cleanPath);
|
|
444
448
|
if (!watchObj) continue;
|
|
445
|
-
if (!watchObj.
|
|
449
|
+
if (!watchObj.ranges.some(x => x.finalPath === path && x.authorityId === authorityId)) continue;
|
|
446
450
|
pathsPerAuthority.parentPaths.add(path);
|
|
447
451
|
}
|
|
448
452
|
}
|
|
@@ -470,6 +474,7 @@ export class RemoteWatcher {
|
|
|
470
474
|
auditLog("Asking to watch parent path", { path, authorityId, targetNodeThreadId: debugNodeThread(authorityId) });
|
|
471
475
|
}
|
|
472
476
|
}
|
|
477
|
+
|
|
473
478
|
logErrors(RemoteWatcher.REMOTE_WATCH_FUNCTION(config, authorityId));
|
|
474
479
|
}
|
|
475
480
|
});
|
|
@@ -525,19 +530,27 @@ export class RemoteWatcher {
|
|
|
525
530
|
}
|
|
526
531
|
let suppressedRewatches = new Set<string>();
|
|
527
532
|
for (let path of config.parentPaths) {
|
|
528
|
-
let
|
|
533
|
+
let cleanPath = hack_stripPackedPath(path);
|
|
534
|
+
let watchObj = this.remoteWatchParents2.get(cleanPath);
|
|
529
535
|
if (!watchObj) continue;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
// Only if we are still watching it (which most of the time, we won't be)
|
|
533
|
-
if (!this.remoteWatchPaths.has(suppressedWatch)) continue;
|
|
534
|
-
suppressedRewatches.add(suppressedWatch);
|
|
535
|
-
auditLog("remoteWatcher inner SUPPRESSED rewatched", { path: suppressedWatch });
|
|
536
|
+
for (let range of watchObj.ranges) {
|
|
537
|
+
range.inputPaths.delete(path);
|
|
536
538
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
+
let removedRanges = watchObj.ranges.filter(x => x.inputPaths.size === 0);
|
|
540
|
+
watchObj.ranges = watchObj.ranges.filter(x => x.inputPaths.size > 0);
|
|
541
|
+
if (watchObj.ranges.length === 0) {
|
|
542
|
+
this.remoteWatchParents2.delete(cleanPath);
|
|
543
|
+
}
|
|
544
|
+
for (let removedRange of removedRanges) {
|
|
545
|
+
innerParentPathRemove(removedRange.finalPath, removedRange.authorityId);
|
|
546
|
+
// Rewatch anything that we suppressed watching (which happens when STOP_KEYS_DOUBLE_SENDS is set)
|
|
547
|
+
for (let suppressedWatch of removedRange.suppressedWatches) {
|
|
548
|
+
// Only if we are still watching it (which most of the time, we won't be)
|
|
549
|
+
if (!this.remoteWatchPaths.has(suppressedWatch)) continue;
|
|
550
|
+
suppressedRewatches.add(suppressedWatch);
|
|
551
|
+
auditLog("remoteWatcher inner SUPPRESSED rewatched", { path: suppressedWatch });
|
|
552
|
+
}
|
|
539
553
|
}
|
|
540
|
-
this.remoteWatchParents.delete(path);
|
|
541
554
|
}
|
|
542
555
|
if (suppressedRewatches.size > 0) {
|
|
543
556
|
this.internalWatchLatest({
|
|
@@ -573,26 +586,22 @@ export class RemoteWatcher {
|
|
|
573
586
|
@measureFnc
|
|
574
587
|
public getExistingWatchRemoteNodeId(path: string): string | undefined {
|
|
575
588
|
let parentPath = getParentPathStr(path);
|
|
576
|
-
let parentWatchObj = this.
|
|
589
|
+
let parentWatchObj = this.remoteWatchParents2.get(parentPath);
|
|
577
590
|
if (parentWatchObj) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
}
|
|
591
|
+
let route = PathRouter.getRouteChildKey(path);
|
|
592
|
+
let range = parentWatchObj.ranges.find(x => x.start <= route && route < x.end);
|
|
593
|
+
if (range) {
|
|
594
|
+
return range.authorityId;
|
|
590
595
|
}
|
|
591
596
|
// 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
|
|
597
|
+
// But... I also don't see the harm in checking remoteWatchPaths
|
|
593
598
|
}
|
|
594
599
|
return this.remoteWatchPaths.get(path);
|
|
595
600
|
}
|
|
601
|
+
public getValueWatchRemoteNodeId(path: string): string | undefined {
|
|
602
|
+
return this.remoteWatchPaths.get(path);
|
|
603
|
+
}
|
|
604
|
+
|
|
596
605
|
public getAllRemoteWatchedNodeIds() {
|
|
597
606
|
return Array.from(new Set(this.remoteWatchPaths.values()));
|
|
598
607
|
}
|
|
@@ -611,7 +620,6 @@ export class RemoteWatcher {
|
|
|
611
620
|
// cases where an authority is in a broken state.
|
|
612
621
|
|
|
613
622
|
let pathsToWatch = new Set<string>();
|
|
614
|
-
let parentPathsToWatch = new Set<string>();
|
|
615
623
|
for (let [path, nodeId] of this.remoteWatchPaths) {
|
|
616
624
|
if (nodeId === authorityNodeId) {
|
|
617
625
|
pathsToWatch.add(path);
|
|
@@ -621,19 +629,30 @@ export class RemoteWatcher {
|
|
|
621
629
|
for (let path of pathsToWatch) {
|
|
622
630
|
this.remoteWatchPaths.delete(path);
|
|
623
631
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
632
|
+
let parentRemotePathsToUnwatch = new Set<string>();
|
|
633
|
+
let parentPathsToRewatch = new Set<string>();
|
|
634
|
+
for (let [cleanPath, watchObj] of this.remoteWatchParents2) {
|
|
635
|
+
let ranges = watchObj.ranges.filter(x => x.authorityId === authorityNodeId);
|
|
636
|
+
watchObj.ranges = watchObj.ranges.filter(x => x.authorityId !== authorityNodeId);
|
|
637
|
+
if (watchObj.ranges.length === 0) {
|
|
638
|
+
this.remoteWatchParents2.delete(cleanPath);
|
|
639
|
+
}
|
|
640
|
+
for (let range of ranges) {
|
|
641
|
+
for (let inputPath of range.inputPaths) {
|
|
642
|
+
parentPathsToRewatch.add(inputPath);
|
|
643
|
+
}
|
|
644
|
+
parentRemotePathsToUnwatch.add(range.finalPath);
|
|
628
645
|
}
|
|
629
646
|
}
|
|
630
647
|
let paths = Array.from(pathsToWatch);
|
|
631
|
-
let parentPaths = Array.from(parentPathsToWatch);
|
|
632
648
|
|
|
633
|
-
logErrors(RemoteWatcher.REMOTE_UNWATCH_FUNCTION({
|
|
649
|
+
logErrors(RemoteWatcher.REMOTE_UNWATCH_FUNCTION({
|
|
650
|
+
paths,
|
|
651
|
+
parentPaths: Array.from(parentRemotePathsToUnwatch)
|
|
652
|
+
}, authorityNodeId));
|
|
634
653
|
this.internalWatchLatest({
|
|
635
654
|
paths: Array.from(pathsToWatch),
|
|
636
|
-
parentPaths: Array.from(
|
|
655
|
+
parentPaths: Array.from(parentPathsToRewatch),
|
|
637
656
|
debugName: "refreshAllWatches",
|
|
638
657
|
});
|
|
639
658
|
});
|
|
@@ -641,7 +660,7 @@ export class RemoteWatcher {
|
|
|
641
660
|
|
|
642
661
|
public getWatchReason(path: string): "direct" | "parent" | undefined {
|
|
643
662
|
if (this.remoteWatchPaths.has(path)) return "direct";
|
|
644
|
-
if (this.
|
|
663
|
+
if (this.remoteWatchParents2.has(getParentPathStr(path))) return "parent";
|
|
645
664
|
return undefined;
|
|
646
665
|
}
|
|
647
666
|
}
|
|
@@ -651,5 +670,4 @@ export const remoteWatcher = new RemoteWatcher();
|
|
|
651
670
|
void Promise.resolve().finally(() => {
|
|
652
671
|
pathWatcher.setUnwatchCallback(config => remoteWatcher.unwatchLatest(config));
|
|
653
672
|
pathWatcher.setWatchRemoteCallback(config => remoteWatcher.watchLatest(config));
|
|
654
|
-
authorityStorage.setGetMultiNodesForParent(path => remoteWatcher.getMultiNodesForParent(path));
|
|
655
673
|
});
|
|
@@ -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
|
|
|
@@ -19,6 +19,7 @@ import { getStorageDir, getSubFolder } from "../fs";
|
|
|
19
19
|
import fs from "fs";
|
|
20
20
|
import { sha256 } from "js-sha256";
|
|
21
21
|
import { AuthoritySpec, PathRouter } from "../0-path-value-core/PathRouter";
|
|
22
|
+
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
export interface AliveChecker<T = unknown> {
|
|
@@ -34,7 +35,7 @@ export interface AliveChecker<T = unknown> {
|
|
|
34
35
|
* NOTE: The value might only have a single heavily nested value, not being a valid object. If the object
|
|
35
36
|
* lookups broken, probably just return false, to clean up the straggling values.
|
|
36
37
|
*/
|
|
37
|
-
isAlive: (value: T, path: string[]) => boolean | unknown;
|
|
38
|
+
isAlive: (value: T, path: string[], valuePath: PathValue | undefined) => boolean | unknown;
|
|
38
39
|
|
|
39
40
|
/** Alias for lockBasedDelete & unsafeLiveReads */
|
|
40
41
|
lock?: boolean;
|
|
@@ -254,7 +255,7 @@ function sandboxedIsAlive(config: {
|
|
|
254
255
|
proxy = proxy[part];
|
|
255
256
|
}
|
|
256
257
|
try {
|
|
257
|
-
return !!atomicRaw(checker.isAlive(proxy, path));
|
|
258
|
+
return !!atomicRaw(checker.isAlive(proxy, path, undefined));
|
|
258
259
|
} catch {
|
|
259
260
|
config.errors.count++;
|
|
260
261
|
return true;
|
|
@@ -308,7 +309,7 @@ function sandboxedGetLocks(config: {
|
|
|
308
309
|
|
|
309
310
|
let evalResult = getWatchEvaluator()(() => {
|
|
310
311
|
let value = proxyWatcher.getCallbackPathValue(pathStr);
|
|
311
|
-
return !!atomic(checker.isAlive(value, path));
|
|
312
|
+
return !!atomic(checker.isAlive(pathValueSerializer.getPathValue(value), path, value));
|
|
312
313
|
}, { getReadLocks: true });
|
|
313
314
|
|
|
314
315
|
return evalResult.readLocks!;
|
|
@@ -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
|
}
|