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.
Files changed (55) hide show
  1. package/bin/audit-disk-values.js +7 -0
  2. package/bin/deploy-prefixes.js +7 -0
  3. package/package.json +5 -3
  4. package/src/-a-archives/archiveCache.ts +12 -9
  5. package/src/-a-auth/certs.ts +1 -1
  6. package/src/-c-identity/IdentityController.ts +9 -1
  7. package/src/-f-node-discovery/NodeDiscovery.ts +63 -10
  8. package/src/0-path-value-core/AuthorityLookup.ts +14 -4
  9. package/src/0-path-value-core/PathRouter.ts +247 -117
  10. package/src/0-path-value-core/PathRouterRouteOverride.ts +1 -1
  11. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +4 -2
  12. package/src/0-path-value-core/PathValueCommitter.ts +68 -31
  13. package/src/0-path-value-core/PathValueController.ts +77 -8
  14. package/src/0-path-value-core/PathWatcher.ts +46 -4
  15. package/src/0-path-value-core/ShardPrefixes.ts +6 -0
  16. package/src/0-path-value-core/ValidStateComputer.ts +20 -8
  17. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +18 -55
  18. package/src/0-path-value-core/pathValueArchives.ts +19 -8
  19. package/src/0-path-value-core/pathValueCore.ts +75 -27
  20. package/src/0-path-value-core/startupAuthority.ts +9 -9
  21. package/src/1-path-client/RemoteWatcher.ts +217 -178
  22. package/src/1-path-client/pathValueClientWatcher.ts +6 -11
  23. package/src/2-proxy/pathValueProxy.ts +2 -3
  24. package/src/3-path-functions/PathFunctionRunner.ts +3 -1
  25. package/src/3-path-functions/syncSchema.ts +6 -2
  26. package/src/4-deploy/deployGetFunctionsInner.ts +1 -1
  27. package/src/4-deploy/deployPrefixes.ts +14 -0
  28. package/src/4-deploy/edgeNodes.ts +1 -1
  29. package/src/4-querysub/Querysub.ts +17 -5
  30. package/src/4-querysub/QuerysubController.ts +21 -10
  31. package/src/4-querysub/predictionQueue.tsx +3 -0
  32. package/src/4-querysub/querysubPrediction.ts +27 -20
  33. package/src/5-diagnostics/nodeMetadata.ts +17 -0
  34. package/src/diagnostics/NodeConnectionsPage.tsx +167 -0
  35. package/src/diagnostics/NodeViewer.tsx +11 -15
  36. package/src/diagnostics/PathDistributionInfo.tsx +102 -0
  37. package/src/diagnostics/SyncTestPage.tsx +19 -8
  38. package/src/diagnostics/auditDiskValues.ts +221 -0
  39. package/src/diagnostics/auditDiskValuesEntry.ts +43 -0
  40. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +5 -1
  41. package/src/diagnostics/logs/TimeRangeSelector.tsx +3 -3
  42. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +2 -0
  43. package/src/diagnostics/managementPages.tsx +10 -1
  44. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +3 -2
  45. package/src/diagnostics/pathAuditer.ts +21 -0
  46. package/src/path.ts +9 -2
  47. package/src/rangeMath.ts +41 -0
  48. package/tempnotes.txt +5 -58
  49. package/test.ts +13 -295
  50. package/src/diagnostics/benchmark.ts +0 -139
  51. package/src/diagnostics/runSaturationTest.ts +0 -416
  52. package/src/diagnostics/satSchema.ts +0 -64
  53. package/src/test/mongoSatTest.tsx +0 -55
  54. package/src/test/satTest.ts +0 -193
  55. 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
- // inputPath =>
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;
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
- // Watches which we suppressed, due to this parent sync. If this parent sync stops,
48
- // we will need to re-send the watches.
49
- suppressedWatches: Set<string>;
50
- fullNodeCount: number;
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
- for (let [path, watchObj] of this.remoteWatchParents.entries()) {
73
- if (typeof watchObj.nodesId === "string") {
74
- if (watchObj.nodesId === authorityId) {
75
- this.remoteWatchParents.delete(path);
76
- parentPaths.push(path);
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
- } else {
79
- if (watchObj.nodesId.has(authorityId)) {
80
- watchObj.nodesId.delete(authorityId);
81
- if (isEmpty(watchObj.nodesId)) {
82
- this.remoteWatchParents.delete(path);
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
- /** Returns [] if we aren't remotely syncing the path. */
153
- public getMultiNodesForParent(path: string): Map<string, unknown> | undefined {
154
- let watchObj = this.remoteWatchParents.get(path);
155
- return watchObj?.nodesId;
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 originalPath of config.parentPaths) {
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(originalPath)) continue;
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
- // 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
- }
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
- watchObj = undefined;
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
- nodesToAdd.set(node.nodeId, { start: curStart, end: nextEnd });
301
- curStart = nextEnd;
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
- let fullRangeCovered = true;
306
- {
307
- let lastValue = rangeStart;
308
- for (let node of nodes) {
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
- lastValue = node.range.end;
314
- }
315
- if (lastValue < rangeEnd) {
316
- fullRangeCovered = false;
300
+ continue;
317
301
  }
318
302
  }
319
- if (!fullRangeCovered) {
320
- if (!this.disconnectedParents.has(originalPath)) {
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(originalPath);
312
+ this.disconnectedParents.add(path);
323
313
  }
324
- } else {
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 = { nodesId: new Map(), fullNodeCount: 0, suppressedWatches: new Set(), watchedPath, useFullPathHash };
346
- this.remoteWatchParents.set(watchedPath, watchObj);
325
+ watchObj = { ranges: [] };
326
+ this.remoteWatchParents2.set(cleanPath, watchObj);
347
327
  }
348
- watchObj.fullNodeCount = nodes.length;
349
- for (let [nodeId, range] of nodesToAdd) {
350
- watchObj.nodesId.set(nodeId, range);
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
- // 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.
354
- for (let authorityId of nodesToAdd.keys()) {
355
- let paths = watchesPerAuthority.get(authorityId);
356
- if (!paths) {
357
- paths = { paths: [], parentPaths: [] };
358
- watchesPerAuthority.set(authorityId, paths);
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 parentPath = getParentPathStr(path);
400
- let parentObj = this.remoteWatchParents.get(parentPath);
401
- if (!parentObj) return true;
402
- parentObj.suppressedWatches.add(path);
403
- auditLog("remoteWatcher outer WATCH SUPPRESSED", { path, remoteNodeId: authorityId });
404
- return false;
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 watchObj = this.remoteWatchParents.get(path);
458
+ let cleanPath = hack_stripPackedPath(path);
459
+ let watchObj = this.remoteWatchParents2.get(cleanPath);
444
460
  if (!watchObj) continue;
445
- if (!watchObj.nodesId.has(authorityId)) continue;
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 watchObj = this.remoteWatchParents.get(path);
545
+ let cleanPath = hack_stripPackedPath(path);
546
+ let watchObj = this.remoteWatchParents2.get(cleanPath);
529
547
  if (!watchObj) continue;
530
- // Rewatch anything that we suppressed watching (which happens when STOP_KEYS_DOUBLE_SENDS is set)
531
- for (let suppressedWatch of watchObj.suppressedWatches) {
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 });
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 authorityId of watchObj.nodesId.keys()) {
538
- innerParentPathRemove(path, authorityId);
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 parentPath = getParentPathStr(path);
576
- let parentWatchObj = this.remoteWatchParents.get(parentPath);
577
- if (parentWatchObj) {
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
- for (let [path, watchObj] of this.remoteWatchParents) {
625
- if (watchObj.nodesId.has(authorityNodeId)) {
626
- watchObj.nodesId.delete(authorityNodeId);
627
- parentPathsToWatch.add(path);
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({ paths, parentPaths }, authorityNodeId));
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(parentPathsToWatch),
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.remoteWatchParents.has(getParentPathStr(path))) return "parent";
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 { SocketFunction } from "socket-function/SocketFunction";
18
- import { cache, lazy } from "socket-function/src/caching";
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, PathValueController } from "../0-path-value-core/PathValueController";
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 { MaybePromise } from "socket-function/src/types";
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 debugbreak from "debugbreak";
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 { auditLog } from "../0-path-value-core/auditLogs";
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(MAX_CHANGE_AGE, function clearProxyCache() {
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
- void Promise.resolve().then(() => {
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
  }