querysub 0.402.0 → 0.404.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.cursorrules +2 -0
  2. package/bin/audit-imports.js +4 -0
  3. package/package.json +7 -4
  4. package/spec.txt +77 -0
  5. package/src/-a-archives/archiveCache.ts +9 -4
  6. package/src/-a-archives/archivesBackBlaze.ts +1039 -1039
  7. package/src/-a-auth/certs.ts +0 -12
  8. package/src/-c-identity/IdentityController.ts +12 -3
  9. package/src/-f-node-discovery/NodeDiscovery.ts +32 -26
  10. package/src/-g-core-values/NodeCapabilities.ts +12 -2
  11. package/src/0-path-value-core/AuthorityLookup.ts +239 -0
  12. package/src/0-path-value-core/LockWatcher2.ts +150 -0
  13. package/src/0-path-value-core/PathRouter.ts +535 -0
  14. package/src/0-path-value-core/PathRouterRouteOverride.ts +72 -0
  15. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +65 -0
  16. package/src/0-path-value-core/PathValueCommitter.ts +222 -488
  17. package/src/0-path-value-core/PathValueController.ts +277 -239
  18. package/src/0-path-value-core/PathWatcher.ts +534 -0
  19. package/src/0-path-value-core/ShardPrefixes.ts +31 -0
  20. package/src/0-path-value-core/ValidStateComputer.ts +303 -0
  21. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +1 -1
  22. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +80 -44
  23. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +13 -16
  24. package/src/0-path-value-core/auditLogs.ts +2 -0
  25. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +97 -0
  26. package/src/0-path-value-core/pathValueArchives.ts +490 -492
  27. package/src/0-path-value-core/pathValueCore.ts +195 -1492
  28. package/src/0-path-value-core/startupAuthority.ts +74 -0
  29. package/src/1-path-client/RemoteWatcher.ts +100 -83
  30. package/src/1-path-client/pathValueClientWatcher.ts +808 -815
  31. package/src/2-proxy/PathValueProxyWatcher.ts +10 -8
  32. package/src/2-proxy/archiveMoveHarness.ts +182 -214
  33. package/src/2-proxy/garbageCollection.ts +9 -8
  34. package/src/2-proxy/schema2.ts +21 -1
  35. package/src/3-path-functions/PathFunctionHelpers.ts +206 -180
  36. package/src/3-path-functions/PathFunctionRunner.ts +943 -766
  37. package/src/3-path-functions/PathFunctionRunnerMain.ts +5 -3
  38. package/src/3-path-functions/pathFunctionLoader.ts +2 -2
  39. package/src/3-path-functions/syncSchema.ts +592 -521
  40. package/src/4-deploy/deployFunctions.ts +19 -4
  41. package/src/4-deploy/deployGetFunctionsInner.ts +8 -2
  42. package/src/4-deploy/deployMain.ts +51 -68
  43. package/src/4-deploy/edgeClientWatcher.tsx +1 -1
  44. package/src/4-deploy/edgeNodes.ts +2 -2
  45. package/src/4-dom/qreact.tsx +2 -4
  46. package/src/4-dom/qreactTest.tsx +7 -13
  47. package/src/4-querysub/Querysub.ts +21 -8
  48. package/src/4-querysub/QuerysubController.ts +45 -29
  49. package/src/4-querysub/permissions.ts +2 -2
  50. package/src/4-querysub/querysubPrediction.ts +80 -70
  51. package/src/4-querysub/schemaHelpers.ts +5 -1
  52. package/src/5-diagnostics/GenericFormat.tsx +14 -9
  53. package/src/archiveapps/archiveGCEntry.tsx +9 -2
  54. package/src/archiveapps/archiveJoinEntry.ts +87 -84
  55. package/src/archiveapps/archiveMergeEntry.tsx +2 -0
  56. package/src/bits.ts +19 -0
  57. package/src/config.ts +21 -3
  58. package/src/config2.ts +23 -48
  59. package/src/deployManager/components/DeployPage.tsx +7 -3
  60. package/src/deployManager/machineSchema.ts +4 -1
  61. package/src/diagnostics/ActionsHistory.ts +3 -8
  62. package/src/diagnostics/AuditLogPage.tsx +2 -3
  63. package/src/diagnostics/FunctionCallInfo.tsx +141 -0
  64. package/src/diagnostics/FunctionCallInfoState.ts +162 -0
  65. package/src/diagnostics/MachineThreadInfo.tsx +1 -1
  66. package/src/diagnostics/NodeViewer.tsx +37 -48
  67. package/src/diagnostics/SyncTestPage.tsx +241 -0
  68. package/src/diagnostics/auditImportViolations.ts +185 -0
  69. package/src/diagnostics/listenOnDebugger.ts +3 -3
  70. package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +10 -4
  71. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +2 -2
  72. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +24 -22
  73. package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +1 -1
  74. package/src/diagnostics/logs/diskLogGlobalContext.ts +1 -0
  75. package/src/diagnostics/logs/errorNotifications2/logWatcher.ts +1 -3
  76. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +39 -17
  77. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +4 -6
  78. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleInstanceTableView.tsx +36 -5
  79. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +19 -5
  80. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +15 -7
  81. package/src/diagnostics/logs/lifeCycleAnalysis/NestedLifeCycleInfo.tsx +28 -106
  82. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMatching.ts +2 -0
  83. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMisc.ts +0 -0
  84. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +18 -7
  85. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +3 -0
  86. package/src/diagnostics/managementPages.tsx +10 -3
  87. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +20 -26
  88. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +6 -4
  89. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +2 -2
  90. package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +7 -9
  91. package/src/diagnostics/misc-pages/SnapshotViewer.tsx +23 -12
  92. package/src/diagnostics/misc-pages/archiveViewerShared.tsx +1 -1
  93. package/src/diagnostics/pathAuditer.ts +486 -0
  94. package/src/diagnostics/pathAuditerCallback.ts +20 -0
  95. package/src/diagnostics/watchdog.ts +8 -1
  96. package/src/library-components/URLParam.ts +1 -1
  97. package/src/misc/hash.ts +1 -0
  98. package/src/path.ts +21 -7
  99. package/src/server.ts +54 -47
  100. package/src/user-implementation/loginEmail.tsx +1 -1
  101. package/tempnotes.txt +67 -0
  102. package/test.ts +288 -95
  103. package/src/0-path-value-core/NodePathAuthorities.ts +0 -1057
  104. package/src/0-path-value-core/PathController.ts +0 -1
  105. package/src/5-diagnostics/diskValueAudit.ts +0 -218
  106. package/src/5-diagnostics/memoryValueAudit.ts +0 -438
  107. package/src/archiveapps/lockTest.ts +0 -127
@@ -1,37 +1,34 @@
1
- import debugbreak from "debugbreak";
2
1
  import { SocketFunction } from "socket-function/SocketFunction";
3
- import { cache, cacheArgsEqual, lazy } from "socket-function/src/caching";
4
- import { addEpsilons, getBufferFraction, minusEpsilon } from "../bits";
5
- import { errorToUndefined, ignoreErrors, logErrors } from "../errors";
6
- import { appendToPathStr, getParentPathStr, getPathDepth, getPathFromStr, getPathIndexAssert, getPathStr, hack_getPackedPathSuffix, hack_setPackedPathSuffix, hack_stripPackedPath, removePathLastPart, rootPathStr } from "../path";
7
- import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
8
- import { IdentityController_getOwnPubKeyShort, debugNodeId, debugNodeThread } from "../-c-identity/IdentityController";
2
+ import { lazy } from "socket-function/src/caching";
3
+ import { addEpsilons, minusEpsilon } from "../bits";
4
+ import { logErrors } from "../errors";
5
+ import { appendToPathStr, getParentPathStr, getPathDepth, getPathIndexAssert, hack_getPackedPathSuffix, hack_setPackedPathSuffix, hack_stripPackedPath } from "../path";
6
+ import { measureFnc, measureWrap } from "socket-function/src/profiling/measure";
7
+ import { IdentityController_getOwnPubKeyShort } from "../-c-identity/IdentityController";
9
8
  import { pathValueArchives } from "./pathValueArchives";
10
- import { blue, magenta, red, yellow } from "socket-function/src/formatting/logColors";
11
- import { PathValueController, pathValueCommitter } from "./PathValueController";
12
- import { batchFunction, delay, runInfinitePoll } from "socket-function/src/batching";
13
- import { ActionsHistory } from "../diagnostics/ActionsHistory";
14
- import { markArrayAsSplitable } from "socket-function/src/fixLargeNetworkCalls";
15
- import { registerDynamicResource, registerMapArrayResource, registerResource } from "../diagnostics/trackResources";
16
- import { binarySearchIndex, isNode, isNodeTrue, last, promiseObj, sort, timeInHour, timeInMinute, timeInSecond, QueueLimited } from "socket-function/src/misc";
17
- import { isNodeTrusted, isTrusted, isTrustedByNode } from "../-d-trust/NetworkTrust2";
18
- import { AuthorityPath, LOCAL_DOMAIN, LOCAL_DOMAIN_PATH, pathValueAuthority2 } from "./NodePathAuthorities";
9
+ import { blue } from "socket-function/src/formatting/logColors";
10
+ import { delay, runInfinitePoll } from "socket-function/src/batching";
11
+ import { registerMapArrayResource, registerResource } from "../diagnostics/trackResources";
12
+ import { isNode, isNodeTrue, sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
19
13
  import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
14
+ import { AuthoritySpec, PathRouter } from "./PathRouter";
20
15
  import { formatTime } from "socket-function/src/formatting/format";
21
- import { getOwnNodeId, isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
22
- import { getNodeIdDomain, getNodeIdDomainMaybeUndefined, getNodeIdIP } from "socket-function/src/nodeCache";
23
- import { getMachineId } from "../-a-auth/certs";
16
+ import { isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
24
17
  import { PromiseRace } from "../../src/misc/PromiseRace";
25
18
 
26
19
  import yargs from "yargs";
27
- import { sha256 } from "js-sha256";
28
20
  import { PromiseObj } from "../promise";
29
- import { ClientWatcher } from "../1-path-client/pathValueClientWatcher";
30
21
  import { auditLog, isDebugLogEnabled } from "./auditLogs";
31
- import { logDisk } from "../diagnostics/logs/diskLogger";
32
- import { isDiskAudit } from "../config";
33
-
34
-
22
+ import { ellipsisMiddle } from "../misc";
23
+ import { fastHash } from "../misc/hash";
24
+ import { authorityLookup } from "./AuthorityLookup";
25
+ import { onPathInteracted } from "../diagnostics/pathAuditerCallback";
26
+ import { filterChildPathsBase } from "./hackedPackedPathParentFiltering";
27
+
28
+ setImmediate(async () => {
29
+ // Import everything will dynamically import, so the client side can tell that it's required.
30
+ await import("./PathWatcher");
31
+ });
35
32
  let yargObj = isNodeTrue() && yargs(process.argv)
36
33
  .option("noarchive", { type: "boolean", alias: ["noarchives"], desc: "Don't save writes to disk. Still reads from disk." })
37
34
  .argv || {}
@@ -71,6 +68,7 @@ export const CLIENTSIDE_PREDICT_LEEWAY = 500;
71
68
  * to write well before this time.
72
69
  * - This has to be at least MAX_CHANGE_AGE * 4.5 + the time to serialize and
73
70
  * send our data to remote storage.
71
+ * TODO: Shouldn't this be MAX_TIME_UNTIL_DISK_FLUSH, which properly takes into account the parts (and adds a buffer)
74
72
  */
75
73
  export const ARCHIVE_FLUSH_LIMIT = Math.max(MAX_CHANGE_AGE * 10, timeInMinute * 60);
76
74
 
@@ -96,6 +94,17 @@ export const UNDEFINED_MEMORY_CLEANUP_DELAY = MAX_CHANGE_AGE * 2;
96
94
 
97
95
  export const ARCHIVE_LOOP_DELAY = MAX_CHANGE_AGE * 2;
98
96
 
97
+ export const MIN_WAIT_TIME_UNTIL_DISK_FLUSH = MAX_CHANGE_AGE * 2;
98
+ export const RAND_ADD_TIME_UNTIL_DISK_FLUSH = MAX_CHANGE_AGE * 0.5;
99
+
100
+ export const MAX_TIME_UNTIL_DISK_FLUSH = (
101
+ ARCHIVE_LOOP_DELAY
102
+ + MIN_WAIT_TIME_UNTIL_DISK_FLUSH
103
+ + RAND_ADD_TIME_UNTIL_DISK_FLUSH
104
+ // Plus, add a lot of buffer time
105
+ + MAX_CHANGE_AGE * 5
106
+ );
107
+
99
108
  /** Both of these are just targets, we might exceed them. */
100
109
  export const FILE_VALUE_COUNT_LIMIT = 50_000;
101
110
  export const FILE_SIZE_LIMIT = 50_000_000;
@@ -143,6 +152,10 @@ export type Time = {
143
152
  export const epochTime: Time = { time: 0, version: 0, creatorId: 0 };
144
153
  Object.freeze(epochTime);
145
154
 
155
+ export function createMissingEpochValue(path: string): PathValue {
156
+ return { path, value: undefined, canGCValue: true, isTransparent: true, valid: true, time: epochTime, locks: [], lockCount: 0, event: false };
157
+ }
158
+
146
159
  /** `timeMinusEpsilon(time)` < time BUT, there is never a case where
147
160
  * `timeMinusEpsilon(time) < otherTime < time` (there is no gap between the times)
148
161
  */
@@ -183,13 +196,6 @@ export type ReadLock = {
183
196
  // is pretty big anyways, but... eh...). HOWEVER, sending if we are reading
184
197
  // undefined is useful, and makes garbage collection possible.
185
198
  readIsTransparent: boolean;
186
-
187
- /** A clientside only value to cause rejected values to be used as valid values for a bit, to fix
188
- * the case where we receive the function evaluation before the replacement values (which is
189
- * actually fairly common, due to how permissions work, so that existing paths are faster,
190
- * but new paths are slower).
191
- */
192
- keepRejectedUntil?: number;
193
199
  };
194
200
  // NOTE: WriteState is a pared down PathValue for ReadLocks to consume.
195
201
  // 1) This helps keep them separate, as values you read are very different from values
@@ -222,6 +228,9 @@ export type PathValue = {
222
228
  path: string;
223
229
  /** @deprecated ALWAYS access value using pathValueSerializer.getPathValue(), unless making a shallow copy. */
224
230
  value: Value;
231
+ /** Every Time+path combination will be globally unique. For a transaction all parts will have the same Time.
232
+ * PathValues with the same Time MUST HAVE the SAME locks arrays.
233
+ */
225
234
  time: Time;
226
235
  /** @deprecated NOT deprecated, just remember when you set this ALWAYS set lockCount, otherwise
227
236
  * valid checking breaks!
@@ -235,12 +244,8 @@ export type PathValue = {
235
244
  */
236
245
  lockCount: number;
237
246
 
238
- /** value === undefined
239
- * - MUST be set if value === undefined
240
- * - We only GC values if they aren't eclipsing any values (or if we also remove the values they are eclipsing
241
- * at the same time, in an atomic manner).
242
- * - Makes reading more efficient (so we don't have to worry about lazily reading values), and makes our
243
- * intentions more clear.
247
+ /** This means that the value is equivalent to having no value, and so we can remove it. This is slightly different than is transparent, which means we can read through the value. Sometimes it's transparent, but it's a special flag, and so we can't GC it.
248
+ * - When canGCValue isTransparent MUST be set (it should be a tri-state, not 2 flags). If you're equivalent to nothing, and nothing is obviously transparent, then you must be transparent.
244
249
  */
245
250
  canGCValue?: boolean;
246
251
 
@@ -269,14 +274,10 @@ export type PathValue = {
269
274
  isTransparent?: boolean;
270
275
 
271
276
 
272
- /** Authorities will clobber this, but non-authorities might use this?
273
- * - This is mostly an in-memory value, and not meant for storage or transmission.
274
- * IMPORTANT! The ONLY reason a PathValue can be rejected is if one of it's locks are rejected
275
- * (due to a conflict, or that lock value being rejected itself). See call prediction for
276
- * how this can be done to allow arbitrary rejection (as in, locking a large time range,
277
- * on a unique path, and then writing to that path when we want to cause a rejection).
278
- */
279
- valid?: boolean;
277
+ /** This should only be set by ValidStateComputer.computeValidStates. IF you want to change the valid state of something, make a shallow copy, and pass it to pathValueCommitter.ingestValuesAndValidStates()
278
+ * - Generally, it only makes sense to be read by that function as well, and code inside of authorityStorage
279
+ * - Generally this is whether or not the locks are accepted and have no contention in their ranges. */
280
+ readonly valid?: boolean;
280
281
 
281
282
  // #region Serialization Only
282
283
 
@@ -296,12 +297,29 @@ export type NodeId = string;
296
297
  export type WatchConfig = {
297
298
  paths: string[];
298
299
  parentPaths: string[];
300
+ // TODO: This should probably be on a per-path basis.
301
+ fullHistory?: boolean;
299
302
  };
300
303
 
301
304
  export type PathValueSnapshot = {
302
305
  values: { [path: string]: PathValue[] };
303
306
  };
304
307
 
308
+ /** NOTE: Oftentimes path values have the exact same read locks, as in the array as triple equals. This can make a lot of code more efficient. */
309
+ export function byLockGroup(values: PathValue[]): Map<ReadLock[], PathValue[]> {
310
+ let byLockGroup = new Map<ReadLock[], PathValue[]>();
311
+ for (let value of values) {
312
+ let locks = value.locks;
313
+ if (locks.length === 0) continue;
314
+ let lockGroup = byLockGroup.get(locks);
315
+ if (!lockGroup) {
316
+ lockGroup = [];
317
+ byLockGroup.set(locks, lockGroup);
318
+ }
319
+ lockGroup.push(value);
320
+ }
321
+ return byLockGroup;
322
+ }
305
323
 
306
324
  export function getNextTime(): Time {
307
325
  return {
@@ -359,46 +377,12 @@ export function debugPathValuePath(pathValue: { time: Time; path: string }): str
359
377
  return `(${debugTime(pathValue.time)}) ${pathValue.path}`;
360
378
  }
361
379
  export function debugPathValue(pathValue: PathValue, write?: boolean): string {
362
- return `${debugPathValuePath(pathValue)} ${write ? "=" : "==="} ${String(pathValueSerializer.getPathValue(pathValue))}`;
380
+ return `${debugPathValuePath(pathValue)} ${write ? "=" : "==="} ${ellipsisMiddle(String(pathValueSerializer.getPathValue(pathValue)), 200)}`;
363
381
  }
364
382
  export function debugTime(time: Time): string {
365
383
  return `${time.time}[${time.version}]@${time.creatorId.toString().slice(4, 12)}`;
366
384
  }
367
385
 
368
- export function filterChildPaths(parentPath: string, packedSuffix: string, paths: Set<string> | undefined) {
369
- if (!packedSuffix || !paths) return paths;
370
- return filterChildPathsBase(parentPath, packedSuffix, paths);
371
- }
372
-
373
- /** Returns a number between 0 (inclusive) and 1 (exclusive)
374
- * - See matchesParentRangeFilter, matchesParentRangeFilterPart, and filterChildPaths.
375
- * FOR INTERNAL USE ONLY!
376
- */
377
- export function __getRoutingHash(key: string): number {
378
- let hash = sha256(key);
379
- return getBufferFraction(Buffer.from(hash, "hex"));
380
- }
381
- // NOTE: Assumes fullPath is already a child of parentPath, and only checks for hash
382
- export function matchesParentRangeFilter(config: {
383
- parentPath: string;
384
- fullPath: string;
385
- start: number;
386
- end: number;
387
- }) {
388
- if (config.start === 0 && config.end === 1) return true;
389
- let part = getPathIndexAssert(config.fullPath, getPathDepth(config.parentPath));
390
- let hash = __getRoutingHash(part);
391
- return config.start <= hash && hash < config.end;
392
- }
393
- export function matchesParentRangeFilterPart(config: {
394
- part: string;
395
- start: number;
396
- end: number;
397
- }) {
398
- if (config.start === 0 && config.end === 1) return true;
399
- let hash = __getRoutingHash(config.part);
400
- return config.start <= hash && hash < config.end;
401
- }
402
386
 
403
387
  let getCompressNetworkBase = () => false;
404
388
  export const registerGetCompressNetwork = (fnc: () => boolean) => { getCompressNetworkBase = fnc; };
@@ -407,41 +391,6 @@ export function getCompressNetwork() {
407
391
  }
408
392
 
409
393
 
410
- const filterChildPathsBase = measureWrap(
411
- function filterChildPathsBase(parentPath: string, packedSuffix: string, paths: Set<string>): Set<string> {
412
- let [startFractionStr, endFractionStr] = packedSuffix.split("|");
413
- let startFraction = Number(startFractionStr);
414
- let endFraction = Number(endFractionStr);
415
-
416
- let depth = getPathDepth(parentPath);
417
-
418
- let filtered = new Set<string>();
419
- for (let path of paths) {
420
- let key = getPathIndexAssert(path, depth);
421
- let hash = __getRoutingHash(key);
422
- if (startFraction <= hash && hash < endFraction) {
423
- filtered.add(path);
424
- }
425
- }
426
- //console.log(`Filtered ${paths.size} paths to ${filtered.size} paths`);
427
- return filtered;
428
- }
429
- );
430
- export function encodeParentFilter(config: {
431
- path: string;
432
- startFraction: number;
433
- endFraction: number;
434
- }) {
435
- return hack_setPackedPathSuffix(config.path, `${config.startFraction}|${config.endFraction}`);
436
- }
437
- export function decodeParentFilter(path: string): { start: number, end: number } | undefined {
438
- let packedSuffix = hack_getPackedPathSuffix(path);
439
- if (!packedSuffix) return undefined;
440
- let [startStr, endStr] = packedSuffix.split("|");
441
- return { start: Number(startStr), end: Number(endStr) };
442
-
443
- }
444
-
445
394
  class AuthorityPathValueStorage {
446
395
  // path => sorted by -time (so newest are first)
447
396
  // NOTE: We automatically drop historical values that can no longer influence
@@ -496,6 +445,9 @@ class AuthorityPathValueStorage {
496
445
  time?: Time,
497
446
  readInvalid = false
498
447
  ): PathValue | undefined {
448
+ if (!this.isEventPath(path)) {
449
+ onPathInteracted(path, 0);
450
+ }
499
451
  let overrideValue: PathValue | undefined;
500
452
  if (this.overrides.size > 0) {
501
453
  for (let override of this.overrides) {
@@ -535,19 +487,18 @@ class AuthorityPathValueStorage {
535
487
  }
536
488
  return undefined;
537
489
  }
538
- public getValuePlusHistory(path: string, returnInvalid = false): PathValue[] {
490
+ /** Returns all values valid or invalid. You should check the valid flag if you want to know if it's valid.
491
+ * - Almost every listener wants this for the purposes of doing validation, and therefore they will want to know if values are invalid, as invalid values can cause another value to become invalid or valid.
492
+ * sorted by -time (so newest are first)
493
+ */
494
+ public getValuePlusHistory(path: string): PathValue[] {
495
+ if (!this.isEventPath(path)) {
496
+ onPathInteracted(path, 0);
497
+ }
539
498
  if (this.overrides.size > 0) {
540
499
  throw new Error(`AuthorityPathValueStorage.getValuePlusHistory support for temporaryOverride not implemented yet.`);
541
500
  }
542
- let valuesToReturn: PathValue[] = [];
543
- let values = this.values.get(path);
544
- if (values) {
545
- for (let value of values) {
546
- if (!value.valid && !returnInvalid) continue;
547
- valuesToReturn.push(value);
548
- }
549
- }
550
- return valuesToReturn;
501
+ return this.values.get(path) || [];
551
502
  }
552
503
 
553
504
  // TODO: Optimize this via an index
@@ -560,8 +511,11 @@ class AuthorityPathValueStorage {
560
511
  public getPathsFromParent(parentPath: string): Set<string> | undefined {
561
512
  let packedSuffix = hack_getPackedPathSuffix(parentPath);
562
513
  parentPath = hack_stripPackedPath(parentPath);
563
- let result = this.parentPathLookup.get(parentPath);
564
- return filterChildPaths(parentPath, packedSuffix, result);
514
+ let paths = this.parentPathLookup.get(parentPath);
515
+ if (packedSuffix && paths) {
516
+ paths = filterChildPathsBase(parentPath, packedSuffix, paths);
517
+ }
518
+ return paths;
565
519
  }
566
520
 
567
521
  @measureFnc
@@ -583,7 +537,7 @@ class AuthorityPathValueStorage {
583
537
  // 1) Need to maintain the values for other nodes
584
538
  // 2) Don't need to worry about the values getting out of date,
585
539
  // because we will get any new values whether we want to or not!
586
- if (pathValueAuthority2.isSelfAuthority(path)) return;
540
+ if (PathRouter.isSelfAuthority(path)) return;
587
541
 
588
542
  if (this.DEBUG_UNWATCH) {
589
543
  console.log(blue(`Unsyncing path at ${Date.now()}`), path);
@@ -591,7 +545,6 @@ class AuthorityPathValueStorage {
591
545
 
592
546
  this.isSyncedCache.delete(path);
593
547
  this.removePathFromStorage(path, "unwatched");
594
- writeValidStorage.deleteRemovedPath(path);
595
548
  }
596
549
  public markParentPathAsUnwatched(path: string) {
597
550
  // NOTE: I don't think we have to handle the case where we are the authority,
@@ -615,73 +568,6 @@ class AuthorityPathValueStorage {
615
568
  return time;
616
569
  }
617
570
 
618
-
619
- @measureFnc
620
- public async getSnapshot(authority: AuthorityPath): Promise<PathValueSnapshot> {
621
- let snapshot: PathValueSnapshot = {
622
- values: Object.create(null),
623
- };
624
- let now = Date.now();
625
- let count = 0;
626
- for (let [path, values] of this.values) {
627
- count++;
628
- if (count > 10_000) {
629
- await delay("paintLoop");
630
- count = 0;
631
- }
632
- // NOTE: We can't return extra values, as they might be outdated values which were
633
- // set to undefined, and then deleted, so returning them will create spurious values.
634
- if (!pathValueAuthority2.isInAuthority(authority, path)) continue;
635
- this.clearRedundantOldValues(values, now);
636
- if (values.length === 0) continue;
637
- snapshot.values[path] = values;
638
- }
639
- return snapshot;
640
- }
641
- @measureFnc
642
- public ingestSnapshot(snapshot: PathValueSnapshot) {
643
- // NOTE: It is important that we don't try to archive these values, as they either came
644
- // from the archive, or have already been archived.
645
-
646
- let now = Date.now();
647
-
648
- for (let [path, values] of Object.entries(snapshot.values)) {
649
- let existingValues = this.values.get(path);
650
- for (let value of values) {
651
- if (value.canGCValue) {
652
- this.possiblyUndefinedPaths().value.add(value.path);
653
- }
654
- if (value.event) {
655
- this.eventPaths().value.add(value.path);
656
- }
657
- }
658
- if (!existingValues) {
659
- this.values.set(path, values);
660
- this.addParentPath(path);
661
- } else {
662
- let allValues = existingValues.concat(values);
663
- allValues.sort((a, b) => compareTime(b.time, a.time));
664
- // Remove duplicates
665
- for (let i = 0; i < allValues.length - 1; i++) {
666
- if (allValues[i].time.time === allValues[i + 1].time.time &&
667
- allValues[i].time.creatorId === allValues[i + 1].time.creatorId) {
668
- allValues.splice(i, 1);
669
- i--;
670
- }
671
- }
672
- values = allValues;
673
- }
674
- this.clearRedundantOldValues(values, now);
675
- }
676
- }
677
-
678
- public resetForInitialTrigger(path: string) {
679
- if (isDebugLogEnabled()) {
680
- auditLog("REMOVE FOR INITIAL SYNC", { path });
681
- }
682
- this.removePathFromStorage(path, "reset for initial trigger");
683
- }
684
-
685
571
  public DEBUG_hasAnyValues(path: string) {
686
572
  let values = this.values.get(path);
687
573
  if (!values) return false;
@@ -691,7 +577,7 @@ class AuthorityPathValueStorage {
691
577
  return true;
692
578
  }
693
579
  public isSynced(path: string, ignoreParentSync = false) {
694
- if (pathValueAuthority2.isSelfAuthority(path)) return true;
580
+ if (PathRouter.isSelfAuthority(path)) return true;
695
581
 
696
582
  if (this.isSyncedCache.has(path)) return true;
697
583
 
@@ -718,54 +604,69 @@ class AuthorityPathValueStorage {
718
604
  }
719
605
  public isParentSynced(path: string) {
720
606
  // We have to check a test child path to see if we are a self authority
721
- if (pathValueAuthority2.isSelfAuthority(appendToPathStr(path, ""))) return true;
607
+ if (PathRouter.isSelfAuthority(appendToPathStr(path, ""))) return true;
722
608
 
723
609
  return this.parentsSynced.get(path) === true;
724
610
  }
725
611
 
612
+ /** Obviously just for debugging. Doesn't trigger any watchers, just erases it as if we never had it. There shouldn't be really any downstream caches within the same process at least, and so this should result in the next read having the value missing. If we in the future add downstream caches, we're going to have to invalidate them as well.
613
+ - This is for testing synchronization issues cross process. As any cross-process notifications might be lost. However, internally, we can verify with one hundred percent certainty, if our code is correct, that all of our internal lookups and caching is always in sync.
614
+ */
615
+ public async DEBUG_secretlyDeleteAllValuesWithPath(path: string) {
616
+ this.values.delete(path);
617
+ this.lastDeleteAt.delete(path);
618
+ this.lastDeleteAtOld.delete(path);
619
+ this.lastDeleteAtOlder.delete(path);
620
+ // Actually, we don't want to delete it from the isSynced cache, as we still want the path to be considered synchronized. We just want the values to be wrong, as in missing values.
621
+ // this.parentsSynced.delete(path);
622
+ // this.isSyncedCache.delete(path);
623
+ }
624
+
726
625
  private getMultiNodesForParent: (path: string) => Map<string, unknown> | undefined = () => undefined;
727
626
  public setGetMultiNodesForParent(fnc: (path: string) => Map<string, unknown> | undefined) {
728
627
  this.getMultiNodesForParent = fnc;
729
628
  }
629
+
630
+ public addParentSyncs(parentSyncs: { parentPath: string; sourceNodeId: string }[]) {
631
+
632
+ for (let obj of parentSyncs) {
633
+ let parentPath = obj.parentPath;
634
+ let sourceNodeId = obj.sourceNodeId;
635
+ let prevSynced = this.parentsSynced.get(parentPath);
636
+ if (prevSynced === true) continue;
637
+ let remoteNodeIds = this.getMultiNodesForParent(parentPath);
638
+ // NOTE: If there is only 1 authority, assume we received data from it, as who else would we receive it from?
639
+ if (!remoteNodeIds || remoteNodeIds.size === 1) {
640
+ this.parentsSynced.set(parentPath, true);
641
+ } else {
642
+ if (!prevSynced) {
643
+ prevSynced = new Set(remoteNodeIds.keys());
644
+ this.parentsSynced.set(parentPath, prevSynced);
645
+ }
646
+ prevSynced.delete(sourceNodeId);
647
+ if (prevSynced.size === 0) {
648
+ this.parentsSynced.set(parentPath, true);
649
+ }
650
+ }
651
+ }
652
+ }
653
+
654
+ /** @deprecated Should only be called by ValidStateComputer. Otherwise, the valid states can change and it won't be able to detect them, and so triggering nested dependencies will not work. */
730
655
  @measureFnc
731
656
  public ingestValues(
732
657
  newValues: PathValue[],
733
- parentsSynced: string[] | undefined,
734
- parentSyncedSources: Map<string, string[]> | undefined,
735
- // Skips archiving (used if we received the values from another node, in which case
736
- // not only should the values already be archives, they are likely too old to be archives
737
- // again!)
738
- skipArchive?: "skipArchive"
658
+ config?: { doNotArchive?: boolean },
739
659
  ) {
660
+ for (let value of newValues) {
661
+ if (value.event) continue;
662
+ onPathInteracted(value.path, 1);
663
+ }
664
+ let { doNotArchive } = config ?? {};
740
665
  let now = Date.now();
741
666
 
742
- if (isDebugLogEnabled()) {
667
+ if (isDebugLogEnabled() && !config?.doNotArchive) {
743
668
  for (let value of newValues) {
744
- auditLog("INGEST VALUE", { path: value.path, time: value.time.time });
745
- }
746
- }
747
-
748
- if (parentsSynced) {
749
- for (let parentPath of parentsSynced) {
750
- let prevSynced = this.parentsSynced.get(parentPath);
751
- if (prevSynced === true) continue;
752
- let remoteNodeIds = this.getMultiNodesForParent(parentPath);
753
- let sourceNodeIds = parentSyncedSources?.get(parentPath);
754
- // NOTE: If there is only 1 authority, assume we received data from it, as who else would we receive it from?
755
- if (!remoteNodeIds || remoteNodeIds.size === 1 || !sourceNodeIds) {
756
- this.parentsSynced.set(parentPath, true);
757
- } else {
758
- if (!prevSynced) {
759
- prevSynced = new Set(remoteNodeIds.keys());
760
- this.parentsSynced.set(parentPath, prevSynced);
761
- }
762
- for (let sourceNodeId of sourceNodeIds) {
763
- prevSynced.delete(sourceNodeId);
764
- }
765
- if (prevSynced.size === 0) {
766
- this.parentsSynced.set(parentPath, true);
767
- }
768
- }
669
+ auditLog("INGEST VALUE", { path: value.path, timeId: value.time.time });
769
670
  }
770
671
  }
771
672
 
@@ -778,27 +679,13 @@ class AuthorityPathValueStorage {
778
679
  this.possiblyUndefinedPaths().value.add(value.path);
779
680
  }
780
681
 
781
- if (!skipArchive && !value.event && !value.path.startsWith(LOCAL_DOMAIN_PATH)) {
782
- let authorityPath = pathValueAuthority2.getSelfArchiveAuthority(value.path);
783
-
784
- // Store in pending archive lookup
785
- if (authorityPath) {
786
- let values = this.pendingArchiveValues.get(authorityPath);
787
- if (!values) {
788
- values = [];
789
- this.pendingArchiveValues.set(authorityPath, values);
790
- }
791
- // If our value has no locks it can never be rejected, so if it is the latest, don't bother to keep the others
792
- if (value.lockCount === 0 && values.length > 0 && values.every(x => compareTime(x.time, value.time) < 0)) {
793
- validateLockCount(value);
794
- if (values.length > 1) {
795
- values.splice(1, values.length - 1);
796
- }
797
- values[0] = value;
798
- } else {
799
- values.push(value);
800
- }
682
+ if (!doNotArchive && !value.event && !PathRouter.isLocalPath(value.path) && PathRouter.isSelfAuthority(value.path)) {
683
+ let values = this.pendingArchiveValues.get(value.path);
684
+ if (!values) {
685
+ values = [];
686
+ this.pendingArchiveValues.set(value.path, values);
801
687
  }
688
+ values.push(value);
802
689
  }
803
690
 
804
691
  // Store in global lookup
@@ -816,13 +703,15 @@ class AuthorityPathValueStorage {
816
703
  } else {
817
704
  let prev = values[insertIndex];
818
705
  if (prev && compareTime(prev.time, value.time) === 0) {
706
+ // NOTE: Before, we used to try to just update the valid state. However, it's kind of unsafe to do this as it results in mutating values, which could make our committer miss a valid state change. Assignment should be just as good.
707
+ values[insertIndex] = value;
819
708
  // Just update the valid state (we likely aren't an authority on the path anyways)
820
- prev.valid = value.valid;
709
+ //prev.valid = value.valid;
821
710
  } else {
822
711
  // Special case values that always have no locks, by just setting the value, and never splicing
823
712
  if (insertIndex === 0 && value.lockCount === 0 && values.length <= 1) {
824
713
  validateLockCount(value);
825
- if (!isCoreQuiet) {
714
+ if (!isCoreQuiet && !doNotArchive) {
826
715
  if (values.length === 1) {
827
716
  console.log(`Clobbering ${debugPathValuePath(values[0])} with ${debugPathValuePath(value)}`);
828
717
  }
@@ -835,7 +724,7 @@ class AuthorityPathValueStorage {
835
724
  }
836
725
  }
837
726
  if (!specialGoldenCase) {
838
- this.clearRedundantOldValues(values, now);
727
+ this.clearRedundantOldValues(values, now, doNotArchive);
839
728
  }
840
729
  }
841
730
  }
@@ -850,8 +739,8 @@ class AuthorityPathValueStorage {
850
739
  this.archiveLoop();
851
740
  }
852
741
 
853
- // authorityPath => values
854
- private pendingArchiveValues = registerMapArrayResource("pendingArchiveValues", new Map<AuthorityPath, PathValue[]>());
742
+ // path => values
743
+ private pendingArchiveValues = registerMapArrayResource("pendingArchiveValues", new Map<string, PathValue[]>());
855
744
  private shuttingDown = new PromiseObj<"shutdown">();
856
745
  private pendingShutdownWrites: PromiseObj | undefined;
857
746
 
@@ -875,7 +764,7 @@ class AuthorityPathValueStorage {
875
764
  // Wait long enough for all values to be old enough to have a fixed valid state.
876
765
  // Also wait some extra time randomly, to reduce the chances of archive being
877
766
  // called at the same time on multiple nodes at once.
878
- let delayTime = MAX_CHANGE_AGE * 2 + Math.random() * MAX_CHANGE_AGE * 0.5;
767
+ let delayTime = MIN_WAIT_TIME_UNTIL_DISK_FLUSH + Math.random() * RAND_ADD_TIME_UNTIL_DISK_FLUSH;
879
768
  if (this.shuttingDown.resolved) {
880
769
  delayTime = 0;
881
770
  }
@@ -890,14 +779,16 @@ class AuthorityPathValueStorage {
890
779
  }
891
780
 
892
781
  try {
893
- for (let [authorityPath, allValues] of pending) {
894
- let validValues = allValues
895
- .filter(x => x.valid)
896
- // ALSO, don't archive epochTime values...
897
- .filter(x => x.time.time)
898
- ;
899
- await pathValueArchives.archiveValues(authorityPath, validValues);
900
- }
782
+ // Only take the valid values, and only the latest. We don't need the history, because these values are all old enough that they can't be rejected (And any one that is referencing the non-latest value, by definition, has to be older than that, and therefore old enough that it can also no longer be rejected)
783
+ let finalValues: PathValue[] = [];
784
+ for (let values of pending.values()) {
785
+ // Valid, and not epoch time
786
+ values = values.filter(x => x.valid && x.time.time);
787
+ if (values.length === 0) continue;
788
+ values.sort((a, b) => compareTime(b.time, a.time));
789
+ finalValues.push(values[0]);
790
+ }
791
+ await pathValueArchives.archiveValues(finalValues);
901
792
  } finally {
902
793
  this.pendingShutdownWrites?.resolve();
903
794
  }
@@ -907,12 +798,11 @@ class AuthorityPathValueStorage {
907
798
  if (yargObj.noarchive) return;
908
799
  this.shuttingDown.resolve("shutdown");
909
800
  if (this.pendingArchiveValues.size === 0) return;
910
- await pathValueAuthority2.waitUntilRoutingIsReady();
801
+ let topology = await authorityLookup.getTopology();
911
802
  // If there are other nodes, we CAN'T just archive our values, as some maybe rejected in the future.
912
- let nodes = pathValueAuthority2.getReadNodes(rootPathStr);
913
- let otherReadNodes = nodes.filter(x => !isOwnNodeId(x));
803
+ let otherReadNodes = topology.filter(x => !isOwnNodeId(x.nodeId));
914
804
  if (otherReadNodes.length > 0 && this.pendingArchiveValues.size > 0) {
915
- console.log(`Other authorities (${otherReadNodes.length}), discarding ${this.pendingArchiveValues.size} pending writes`);
805
+ console.log(`Other authorities (${otherReadNodes.length}), discarding ${this.pendingArchiveValues.size} pending writes`, otherReadNodes, topology.filter(x => isOwnNodeId(x.nodeId)));
916
806
  }
917
807
  if (otherReadNodes.length === 0) {
918
808
  // Wait the PromiseRace to finish
@@ -930,7 +820,7 @@ class AuthorityPathValueStorage {
930
820
  // really happen too often (and the real issue is not moving values from memory => storage, which when we do,
931
821
  // we will call this anyways).
932
822
  // NOTE: values are sorted sorted by -time (so newest are first)
933
- public clearRedundantOldValues(values: PathValue[], now: number) {
823
+ public clearRedundantOldValues(values: PathValue[], now: number, doNotArchive?: boolean) {
934
824
  // NOTE: We can also delete golden rejections BEFORE the valid golden value. But...
935
825
  // that is less important, as there are likely to be few rejected values, so this
936
826
  // is really just designed for removing older golden values (which are probably valid).
@@ -953,7 +843,7 @@ class AuthorityPathValueStorage {
953
843
  && values[0].canGCValue
954
844
  && values[0].time.time < now - VALUE_GC_THRESHOLD
955
845
  ) {
956
- if (!isCoreQuiet) {
846
+ if (!isCoreQuiet && !doNotArchive) {
957
847
  console.log(`GCing ALL ${debugPathValuePath(values[0])}`);
958
848
  for (let value of values) {
959
849
  auditLog("GCing", { path: value.path });
@@ -977,7 +867,7 @@ class AuthorityPathValueStorage {
977
867
  if (firstGoldenIndex === 0) {
978
868
  // Remove everything but the latest value
979
869
  let removed = values.splice(1, values.length - 1);
980
- if (!isCoreQuiet) {
870
+ if (!isCoreQuiet && !doNotArchive) {
981
871
  console.log(`GCing everything older than ${debugPathValuePath(values[0])}`);
982
872
  for (let value of removed) {
983
873
  auditLog("GCing", { path: value.path });
@@ -988,7 +878,7 @@ class AuthorityPathValueStorage {
988
878
  if (firstGoldenIndex >= 0 && values.length > firstGoldenIndex) {
989
879
  for (let i = values.length - 1; i > firstGoldenIndex; i--) {
990
880
  let gced = values.pop();
991
- if (!isCoreQuiet && gced) {
881
+ if (!isCoreQuiet && !doNotArchive && gced) {
992
882
  auditLog("GCing", { path: gced.path });
993
883
  }
994
884
  }
@@ -996,7 +886,7 @@ class AuthorityPathValueStorage {
996
886
  }
997
887
  }
998
888
 
999
- private removePathFromStorage(path: string, reason: string) {
889
+ public removePathFromStorage(path: string, reason: string) {
1000
890
  let values = this.values.get(path);
1001
891
  if (values?.length) {
1002
892
  this.lastDeleteAt.set(path, values[0].time);
@@ -1023,7 +913,8 @@ class AuthorityPathValueStorage {
1023
913
  private possiblyUndefinedPaths = lazy((): { value: Set<string> } => {
1024
914
  let pathsToCheckPointer = { value: new Set<string>() };
1025
915
 
1026
- runInfinitePoll(UNDEFINED_MEMORY_CLEANUP_DELAY, () => {
916
+ runInfinitePoll(UNDEFINED_MEMORY_CLEANUP_DELAY, async () => {
917
+ const { pathWatcher } = await import("./PathWatcher");
1027
918
  let now = Date.now();
1028
919
  let deadTime = now - UNDEFINED_MEMORY_CLEANUP_DELAY;
1029
920
  let pathsToCheck = pathsToCheckPointer.value;
@@ -1061,11 +952,16 @@ class AuthorityPathValueStorage {
1061
952
  return pathsToCheckPointer;
1062
953
  });
1063
954
 
955
+ private isEventPath(path: string) {
956
+ return this.eventPaths().value.has(path);
957
+ }
958
+
1064
959
  private eventPaths = lazy((): { value: Set<string> } => {
1065
960
  let pathsToCheckPointer = { value: new Set<string>() };
1066
961
 
1067
962
  let garbageCollectTime = MAX_CHANGE_AGE * 2;
1068
- runInfinitePoll(garbageCollectTime, () => {
963
+ runInfinitePoll(garbageCollectTime, async () => {
964
+ const { pathWatcher } = await import("./PathWatcher");
1069
965
  let deadTime = Date.now() - garbageCollectTime;
1070
966
  let pathsToCheck = pathsToCheckPointer.value;
1071
967
  let nextPathsToCheck = new Set<string>();
@@ -1129,1241 +1025,48 @@ class AuthorityPathValueStorage {
1129
1025
  public __auditValues() {
1130
1026
  return this.values;
1131
1027
  }
1132
- }
1133
- export const authorityStorage = new AuthorityPathValueStorage();
1134
- export const values = authorityStorage;
1135
- export const pathValues = authorityStorage;
1136
-
1137
1028
 
1138
- // If PathValue, it is a local watch
1139
- export type WriteCallback = NodeId | PathValue[];
1140
-
1141
-
1142
- // isOwnNodeId(nodeId) means localOnValueCallback is called (instead of PathValueController.forwardWrites)
1143
- export type PathWatcherCallback = NodeId;
1144
- class PathWatcher {
1145
- private watchers = registerResource("paths|PathWatcher.watchers", new Map<string, {
1146
- watchers: Set<PathWatcherCallback>;
1147
- requestedTime: number;
1148
- receivedTime: number;
1149
- }>());
1150
- // A fast way to lookup parentPath => childPaths in watchers, without having
1151
- // to brute force search.
1152
- private watcherParentToPath = registerResource("paths|PathWatcher.watcherParentToPath", new Map<string, Set<string>>());
1153
- // If we don't limit the length, then this will memory leak. And if we only track
1154
- // the values in watchers, then rolling key syncs will be lost.
1155
- // DOES NOT track local values, as they are used internally, but we aren't waiting for them to be synced.
1156
- private syncHistoryForHarvest = new QueueLimited<{ start: number; end: number; path: string; }>(1_000_000);
1157
- private syncHistoryForDebug = new QueueLimited<{ start: number; end: number; path: string; }>(1_000_000);
1158
-
1159
- //private watchers = registerResource("paths|PathWatcher.watchers", new Map<string, Set<PathWatcherCallback>>());
1160
-
1161
- // realPath => packedPath => watcher => { depth, start, end }[]
1162
- private parentWatchers = registerResource("paths|parentWatchers", new Map<string,
1163
- // packedPath
1164
- Map<string, {
1165
- watchers: Set<PathWatcherCallback>;
1166
- start: number;
1167
- end: number;
1168
- }>
1169
- >());
1170
- private watchersToPaths = registerResource("paths|watchersToPaths", new Map<PathWatcherCallback, {
1171
- paths: Set<string>;
1172
- parents: Set<string>;
1173
- }>());
1174
-
1175
- /** Automatically unwatches on callback disconnect */
1176
- @measureFnc
1177
- public watchPath(config: WatchConfig & {
1178
- callback: PathWatcherCallback;
1179
- debugName?: string;
1180
- noInitialTrigger?: boolean;
1181
- /** Force trigger, even if we are already watching the values. */
1182
- initialTrigger?: boolean;
1183
- }) {
1184
- if (config.callback) {
1185
- this.ensureUnwatchingOnDisconnect(config.callback);
1186
- }
1187
-
1188
- let newPathsWatched = new Set<string>();
1189
- let newParentsWatched = new Set<string>();
1190
-
1191
- let time = Date.now();
1192
-
1193
- for (let path of config.paths) {
1194
- let watchers = this.watchers.get(path);
1195
- if (!watchers) {
1196
- watchers = {
1197
- watchers: new Set(),
1198
- requestedTime: time,
1199
- receivedTime: 0,
1200
- };
1201
- this.watchers.set(path, watchers);
1202
- let parentPath = getParentPathStr(path);
1203
- let parentWatchers = this.watcherParentToPath.get(parentPath);
1204
- if (!parentWatchers) {
1205
- parentWatchers = new Set();
1206
- this.watcherParentToPath.set(parentPath, parentWatchers);
1207
- }
1208
- parentWatchers.add(path);
1209
- }
1210
- if (watchers.watchers.has(config.callback)) continue;
1211
- newPathsWatched.add(path);
1212
- watchers.watchers.add(config.callback);
1213
-
1214
- let watchObj = this.watchersToPaths.get(config.callback);
1215
- if (!watchObj) {
1216
- watchObj = { paths: new Set(), parents: new Set() };
1217
- this.watchersToPaths.set(config.callback, watchObj);
1218
- }
1219
- watchObj.paths.add(path);
1220
- }
1221
- for (let path of config.parentPaths) {
1222
- let basePath = hack_stripPackedPath(path);
1223
- let watchersObj = this.parentWatchers.get(basePath);
1224
- if (!watchersObj) {
1225
- watchersObj = new Map();
1226
- this.parentWatchers.set(basePath, watchersObj);
1227
- }
1228
- let obj = watchersObj.get(path);
1229
- if (!obj) {
1230
- let range = decodeParentFilter(path) || { start: 0, end: 1 };
1231
- obj = { watchers: new Set(), start: range.start, end: range.end };
1232
- watchersObj.set(path, obj);
1233
- }
1234
- if (obj.watchers.has(config.callback)) continue;
1235
- obj.watchers.add(config.callback);
1236
-
1237
- newParentsWatched.add(path);
1238
-
1239
- let watchObj = this.watchersToPaths.get(config.callback);
1240
- if (!watchObj) {
1241
- watchObj = { paths: new Set(), parents: new Set() };
1242
- this.watchersToPaths.set(config.callback, watchObj);
1243
- }
1244
- watchObj.parents.add(path);
1245
- }
1246
-
1247
- if (newPathsWatched.size > 0 || newParentsWatched.size > 0) {
1248
- if (isOwnNodeId(config.callback)) {
1249
- for (let path of newPathsWatched) {
1250
- auditLog("new local WATCH VALUE", { path });
1251
- }
1252
- for (let path of newParentsWatched) {
1253
- auditLog("new local WATCH PARENT", { path });
1254
- }
1255
- } else {
1256
- for (let path of newPathsWatched) {
1257
- auditLog("new non-local WATCH VALUE", { path, watcher: config.callback });
1258
- }
1259
- for (let path of newParentsWatched) {
1260
- auditLog("new non-local WATCH PARENT", { path, watcher: config.callback });
1261
- }
1262
- }
1263
- console.info(`New PathValue watches`, {
1264
- newPathsWatched: newPathsWatched.size,
1265
- newParentsWatched: newParentsWatched.size,
1266
- });
1267
- }
1268
-
1269
- // Treat everything as a new watch
1270
- if (config.initialTrigger) {
1271
- newPathsWatched = new Set(config.paths);
1272
- newParentsWatched = new Set(config.parentPaths);
1273
- }
1274
-
1275
- if (!config.noInitialTrigger) {
1276
- // Trigger all initial values (for paths we have synced)
1277
- let initialValues = new Set<PathValue>();
1278
- let newPaths = new Set(Array.from(newPathsWatched).filter(x => authorityStorage.isSynced(x)));
1279
- for (let path of newParentsWatched) {
1280
- // If we haven't synced the parent, we can't forward it. This is important for proxies
1281
- // (when they receive the parent synced they will forward the values).
1282
- if (!authorityStorage.isParentSynced(path)) continue;
1283
- let paths = authorityStorage.getPathsFromParent(path);
1284
- if (paths) {
1285
- for (let path of paths) {
1286
- newPaths.add(path);
1287
- }
1288
- }
1289
-
1290
- let childPaths = this.watcherParentToPath.get(path);
1291
- if (childPaths) {
1292
- for (let childPath of childPaths) {
1293
- newPaths.add(childPath);
1294
- }
1295
- }
1296
- }
1297
-
1298
- if (newPaths.size > 0 || newParentsWatched.size > 0) {
1299
- for (let path of newPaths) {
1300
- let history = authorityStorage.getValuePlusHistory(path);
1301
- for (let value of history) {
1302
- initialValues.add(value);
1303
- }
1304
- if (history.length === 0) {
1305
- // We always have to send something, or the client won't know when they are ready to show the data!
1306
- initialValues.add({ path, value: undefined, canGCValue: true, isTransparent: true, valid: true, time: epochTime, locks: [], lockCount: 0, event: false });
1307
- }
1308
- }
1309
- this.triggerLatestWatcher(
1310
- config.callback,
1311
- Array.from(initialValues),
1312
- config.parentPaths,
1313
- config.initialTrigger ? "initialTrigger" : undefined,
1314
- );
1315
- }
1316
- }
1317
- }
1318
- // NOTE: Automatically unwatches from remoteWatcher on all paths that are fully unwatched
1319
- @measureFnc
1320
- public unwatchPath(config: WatchConfig & { callback: PathWatcherCallback; }) {
1321
- const { callback } = config;
1322
-
1323
- let fullyUnwatched: WatchConfig = {
1324
- paths: [],
1325
- parentPaths: [],
1326
- };
1327
-
1328
- let pathsWatched = this.watchersToPaths.get(callback);
1329
-
1330
- for (let path of config.parentPaths) {
1331
- if (pathsWatched) {
1332
- pathsWatched.parents.delete(path);
1333
- }
1334
-
1335
- let unpackedPath = hack_stripPackedPath(path);
1336
-
1337
- let watchersObj = this.parentWatchers.get(unpackedPath);
1338
- if (!watchersObj) continue;
1339
- let obj = watchersObj.get(path);
1340
- if (!obj) continue;
1341
- obj.watchers.delete(callback);
1342
-
1343
- if (isOwnNodeId(callback)) {
1344
- auditLog("local UNWATCH PARENT", { path });
1345
- } else {
1346
- auditLog("non-local UNWATCH PARENT", { path, watcher: callback });
1347
- }
1348
-
1349
- if (obj.watchers.size === 0) {
1350
- watchersObj.delete(path);
1351
- this.parentWatchers.delete(path);
1352
- fullyUnwatched.parentPaths.push(path);
1353
- authorityStorage.markParentPathAsUnwatched(path);
1354
-
1355
- if (watchersObj.size === 0) {
1356
- this.parentWatchers.delete(unpackedPath);
1357
-
1358
- let childPaths = authorityStorage.getPathsFromParent(unpackedPath);
1359
- // Destroy values that now have no watchers (no value watchers, and now no parent watcher
1360
- if (childPaths) {
1361
- for (let childPath of childPaths) {
1362
- if (!this.watchers.has(childPath)) {
1363
- authorityStorage.destroyPath(childPath);
1364
- }
1365
- }
1366
- }
1367
- }
1368
- }
1369
- }
1370
-
1371
- // NOTE: Unwatch parents, then values, as parent paths might keep alive value path watches.
1372
- for (let path of config.paths) {
1373
- if (pathsWatched) {
1374
- pathsWatched.paths.delete(path);
1375
- }
1376
-
1377
- let watchers = this.watchers.get(path);
1378
- if (!watchers) continue;
1379
- watchers.watchers.delete(callback);
1380
-
1381
- if (isOwnNodeId(callback)) {
1382
- auditLog("local UNWATCH VALUE", { path });
1383
- } else {
1384
- auditLog("non-local UNWATCH VALUE", { path, watcher: callback });
1385
- }
1386
-
1387
- if (watchers.watchers.size === 0) {
1388
- this.watchers.delete(path);
1389
-
1390
- let parentPath = getParentPathStr(path);
1391
- let parentWatchers = this.watcherParentToPath.get(parentPath);
1392
- if (parentWatchers) {
1393
- parentWatchers.delete(path);
1394
- }
1395
-
1396
- fullyUnwatched.paths.push(path);
1397
- // NOTE: If the parent is being watched, don't destroy the value.
1398
- // - The fact that we only do the check here does mean that if the path is unwatched
1399
- // first and then later the parent path is unwatched we will fail to properly destroy
1400
- // the path. However, in practice, either both are unwatched at the same time, or more
1401
- // likely, the value watch only goes away because it's made redundant by the parent watch.
1402
- if (!this.parentWatchers.has(parentPath)) {
1403
- authorityStorage.destroyPath(path);
1404
- }
1405
- }
1406
- }
1407
-
1408
- if (fullyUnwatched.paths.length > 0 || fullyUnwatched.parentPaths.length > 0) {
1409
- console.info(`Unwatched PathValue watches`, {
1410
- unwatchedPaths: fullyUnwatched.paths.length,
1411
- unwatchedParents: fullyUnwatched.parentPaths.length,
1412
- });
1413
- for (let unwatchCallback of this.unwatchedCallbacks) {
1414
- unwatchCallback(fullyUnwatched);
1415
- }
1029
+ public getValueCount() {
1030
+ let totalCount = 0;
1031
+ for (let values of this.values.values()) {
1032
+ totalCount += values.length;
1416
1033
  }
1034
+ return totalCount;
1417
1035
  }
1418
1036
 
1419
- public unwatchEventPaths(paths: Set<string>) {
1420
- for (let path of paths) {
1421
- let watchers = this.watchers.get(path);
1422
- if (watchers) {
1423
- this.watchers.delete(path);
1424
- for (let watcher of watchers.watchers) {
1425
- let watchObj = this.watchersToPaths.get(watcher);
1426
- if (watchObj) {
1427
- watchObj.paths.delete(path);
1428
- }
1429
- }
1430
- }
1431
- let parentWatchers = this.parentWatchers.get(path);
1432
- if (parentWatchers) {
1433
- this.parentWatchers.delete(path);
1434
- for (let { watchers } of parentWatchers.values()) {
1435
- for (let watcher of watchers) {
1436
- let watchObj = this.watchersToPaths.get(watcher);
1437
- if (watchObj) {
1438
- watchObj.parents.delete(path);
1439
- }
1440
- }
1441
- }
1442
- }
1443
- }
1444
- }
1445
-
1446
- public triggerValuesChanged(valuesChanged: Set<PathValue>, parentsSynced?: string[], initialTriggers?: { values: Set<string>; parentPaths: Set<string> }) {
1447
- let changedPerCallbacks = this.getWatchers(valuesChanged, { parentsSynced, pathsAreSynced: true });
1448
- for (let [watch, changes] of changedPerCallbacks) {
1449
- if (initialTriggers) {
1450
- let valuesFromInitialTrigger = new Set<PathValue>();
1451
- let parentsFromInitialTrigger = new Set<string>();
1452
- let valuesOther = new Set<PathValue>();
1453
- let parentsOther = new Set<string>();
1454
- for (let value of changes) {
1455
- if (initialTriggers.values.has(value.path)) {
1456
- valuesFromInitialTrigger.add(value);
1457
- } else {
1458
- valuesOther.add(value);
1459
- }
1460
- }
1461
- for (let parent of initialTriggers.parentPaths) {
1462
- parentsFromInitialTrigger.add(parent);
1463
- }
1464
- if (parentsSynced) {
1465
- for (let parent of parentsSynced) {
1466
- if (!initialTriggers.parentPaths.has(parent)) {
1467
- parentsOther.add(parent);
1468
- }
1469
- }
1470
- }
1471
-
1472
- if (valuesFromInitialTrigger.size > 0 || parentsFromInitialTrigger.size > 0) {
1473
- this.triggerLatestWatcher(watch, Array.from(valuesFromInitialTrigger), Array.from(parentsFromInitialTrigger), "initialTrigger");
1474
- }
1475
- if (valuesOther.size > 0 || parentsOther.size > 0) {
1476
- this.triggerLatestWatcher(watch, Array.from(valuesOther), Array.from(parentsOther));
1477
- }
1478
- } else {
1479
- this.triggerLatestWatcher(watch, Array.from(changes), parentsSynced);
1480
- }
1481
- }
1482
- }
1483
- public getWatchers<T extends { path: string }>(
1484
- valuesChanged: Set<T>,
1485
- config?: {
1486
- parentsSynced?: string[];
1487
- pathsAreSynced?: boolean;
1488
- }
1489
- ) {
1490
- let changedPerCallbacks: Map<PathWatcherCallback, Set<T>> = new Map();
1491
- let time = Date.now();
1492
- for (let value of valuesChanged) {
1493
- let path = value.path;
1494
- let latestWatches = this.watchers.get(path);
1495
- if (config?.pathsAreSynced && latestWatches && !latestWatches.receivedTime && !path.startsWith(LOCAL_DOMAIN_PATH)) {
1496
- latestWatches.receivedTime = time;
1497
- let obj = { start: latestWatches.requestedTime, end: time, path };
1498
- this.syncHistoryForHarvest.push(obj);
1499
- this.syncHistoryForDebug.push(obj);
1500
- }
1501
-
1502
- function triggerNodeChanged(watcher: NodeId) {
1503
- let changes = changedPerCallbacks.get(watcher);
1504
- if (!changes) {
1505
- changes = new Set();
1506
- changedPerCallbacks.set(watcher, changes);
1507
- }
1508
- // @ts-ignore
1509
- changes.add(value);
1510
- }
1511
-
1512
- if (latestWatches) {
1513
- for (let watch of latestWatches.watchers) {
1514
- triggerNodeChanged(watch);
1515
- }
1516
- }
1517
-
1518
- let parentPath = getParentPathStr(path);
1519
- let latestParentWatches = this.parentWatchers.get(parentPath);
1520
- if (latestParentWatches) {
1521
- let pathRoutingHash: number | undefined = undefined;
1522
- for (let { start, end, watchers } of latestParentWatches.values()) {
1523
- if (!matchesParentRangeFilter({
1524
- parentPath,
1525
- fullPath: path,
1526
- start,
1527
- end,
1528
- })) {
1529
- continue;
1530
- }
1531
- for (let watch of watchers) {
1532
- triggerNodeChanged(watch);
1533
- }
1534
- }
1535
- }
1536
- }
1537
- for (let parentPath of config?.parentsSynced ?? []) {
1538
- let latestParentWatches = this.parentWatchers.get(parentPath);
1539
- if (latestParentWatches) {
1540
- for (let { watchers } of latestParentWatches.values()) {
1541
- for (let watcher of watchers) {
1542
- let changes = changedPerCallbacks.get(watcher);
1543
- if (!changes) {
1544
- changes = new Set();
1545
- changedPerCallbacks.set(watcher, changes);
1546
- }
1547
- }
1548
- }
1549
- }
1550
- }
1551
- return changedPerCallbacks;
1552
- }
1553
-
1554
- public isWatching(path: string): boolean {
1555
- return this.watchers.has(path) || this.parentWatchers.has(getParentPathStr(path));
1556
- }
1557
-
1558
- @measureFnc
1559
- private triggerLatestWatcher(
1560
- watcher: PathWatcherCallback,
1561
- changes: PathValue[],
1562
- parentPaths?: string[],
1563
- initialTrigger?: "initialTrigger"
1564
- ) {
1565
- if (isOwnNodeId(watcher)) {
1566
- for (let callback of this.localTriggerCallbacks) {
1567
- callback(changes, parentPaths ?? []);
1568
- }
1569
- } else {
1570
- if (!isCoreQuiet) {
1571
- console.log(`(${Date.now()}) Sending values to client: ${changes.length} (${watcher})`);
1572
- }
1573
- this.ensureUnwatchingOnDisconnect(watcher);
1574
- ignoreErrors((async () => {
1575
- let allowSource = await isNodeTrusted(watcher) || getNodeIdIP(watcher) === "127.0.0.1";
1576
- let buffers = await pathValueSerializer.serialize(changes, {
1577
- noLocks: true,
1578
- compress: getCompressNetwork(),
1579
- stripSource: !allowSource,
1580
- });
1581
-
1582
- if (isDebugLogEnabled() || isDiskAudit()) {
1583
- for (let pathValue of changes) {
1584
- auditLog("SEND VALUE", {
1585
- path: pathValue.path,
1586
- time: pathValue.time.time,
1587
- watcher,
1588
- nodeId: debugNodeId(watcher),
1589
- targetNodeId: debugNodeId(watcher),
1590
- targetNodeThreadId: debugNodeThread(watcher),
1591
- transparent: pathValue.isTransparent,
1592
- canGC: pathValue.canGCValue,
1593
- });
1594
- }
1595
- }
1596
- await PathValueController.nodes[watcher].forwardWrites(
1597
- buffers,
1598
- parentPaths,
1599
- undefined,
1600
- initialTrigger,
1601
- );
1602
- })());
1603
- }
1604
- }
1605
-
1606
- private ensureUnwatchingOnDisconnect = cache((nodeId: string) => {
1607
- if (isOwnNodeId(nodeId)) return;
1608
- SocketFunction.onNextDisconnect(nodeId, async () => {
1609
- this.ensureUnwatchingOnDisconnect.clear(nodeId);
1610
-
1611
- // Wait while, so Querysub doesn't thrash when clients connect and disconnect
1612
- // TODO: We should be smarter about our wait time, waiting less if we have a lot of resource
1613
- // pressure, and more (potentially forever), if we don't have much resource pressure
1614
- // (memory, cpu, and network).
1615
- setTimeout(() => {
1616
- let watches = this.watchersToPaths.get(nodeId);
1617
- this.watchersToPaths.delete(nodeId);
1618
- if (watches) {
1619
- this.unwatchPath({ paths: Array.from(watches.paths), parentPaths: Array.from(watches.parents), callback: nodeId });
1620
- }
1621
- }, MAX_CHANGE_AGE);
1622
- });
1623
- });
1624
-
1625
- private localTriggerCallbacks = new Set<(changes: PathValue[], parentPaths: string[]) => void>();
1626
- public watchAllLocalTriggers(callback: (changes: PathValue[], parentPaths: string[]) => void) {
1627
- this.localTriggerCallbacks.add(callback);
1628
- }
1629
-
1630
- public unwatchedCallbacks = new Set<(config: WatchConfig) => void>();
1631
- public watchUnwatched(callback: (config: WatchConfig) => void) {
1632
- this.unwatchedCallbacks.add(callback);
1633
- }
1634
-
1635
- public getAllParentWatches() {
1636
- return Array.from(this.parentWatchers.keys());
1637
- }
1638
-
1639
- public debug_harvestSyncTimes(): { start: number; end: number; path: string; }[] {
1640
- let times = this.syncHistoryForHarvest.getAllUnordered();
1641
- this.syncHistoryForHarvest.reset();
1642
- return times;
1643
- }
1644
-
1645
- public debug_getSyncHistory(): { start: number; end: number; path: string; }[] {
1646
- return this.syncHistoryForDebug.getAllUnordered();
1647
- }
1648
- }
1649
- export const pathWatcher = new PathWatcher();
1650
-
1651
-
1652
- class WriteValidStorage {
1653
- // We could have a more efficient storage for non-range, but... we need range storage anyways,
1654
- // so... this is fine.
1655
- // (Not sorted)
1656
- // - May have duplicates for the same path (for lockless writes), always with the same valid state.
1657
- // path => WriteState[]
1658
- // WriteState is UNSORTED (it is only kept around for MAX_CHANGE_AGE, so this should be the fastest way to do it)
1659
- private validStorage = registerMapArrayResource("validStorage", new Map<string, WriteState[]>());
1660
-
1661
- private delayedInvalidate = ((lock: ReadLock, targetTime: number, valueGroupForPredictionHack: PathValue[]) => {
1662
- const tryNow = () => {
1663
- if (Date.now() >= targetTime) {
1664
- setTimeout(tryNow, Date.now() - targetTime + 50);
1665
- return;
1666
- }
1667
- pathValueCommitter.ingestValidStates(valueGroupForPredictionHack.map(x => ({
1668
- path: x.path,
1669
- isValid: true,
1670
- time: x.time,
1671
- isTransparent: x.isTransparent || false,
1672
- })), undefined, "recomputeValidState");
1673
- };
1674
- tryNow();
1675
- });
1676
-
1677
- public getWriteState(readLock: ReadLock, now: number): WriteState {
1678
- let isValid = this.isLockValid(readLock, now);
1679
- return { path: readLock.path, isValid, time: readLock.startTime, isTransparent: readLock.readIsTransparent };
1680
- }
1681
- public isLockValid(readLock: ReadLock, now: number, valueGroupForPredictionHack?: PathValue[]): boolean {
1682
- let isValid = this.isLockValidBase(readLock, now);
1683
- if (!isValid && readLock.keepRejectedUntil && now < readLock.keepRejectedUntil && valueGroupForPredictionHack) {
1684
- isValid = true;
1685
- // TODO: Test this code. We haven't run it, as it is a bit annoying to set up (we need to predict
1686
- // writes to a new location). It will be pretty obvious if it is failing, but... I don't think it will?
1687
- console.log(yellow(`Delaying rejection of ${readLock.path} to try to fix out of order prediction invalidation`));
1688
- // Trigger a recheck when the keepRejectedUntil time is reached
1689
- this.delayedInvalidate(readLock, readLock.keepRejectedUntil, valueGroupForPredictionHack);
1690
- }
1691
- return isValid;
1692
- }
1693
- public isLockValidBase(readLock: ReadLock, now: number): boolean {
1694
- // If it old enough it must be valid, otherwise how would a client NOT know it was invalid
1695
- // (assuming clients clear their reads aftering being disconnecting for long enough).
1696
- if (readLock.startTime.time < now - MAX_CHANGE_AGE) {
1697
- return true;
1698
- }
1699
-
1700
- let values = this.validStorage.get(readLock.path);
1701
- let time = readLock.startTime;
1702
- if (readLock.readIsTransparent) {
1703
- // EDIT: Actually... isn't this done so that rejections can cascade from other nodes, with our node
1704
- // not even evaluating the actual rejection reason, but just directly using the reject value?
1705
- // // EDIT: I *think* that the range check should be sufficient for undefined read locks.
1706
- // // If that is the case, remove isUndefinedAtLock entirely.
1707
- // // - The reason the range check is good enough is that is is checking for values. Actually,
1708
- // // if we always checked for values the range check would always be sufficient. HOWEVER,
1709
- // // the range + valid check is an optimization, so locks don't need to store their values.
1710
- // // But in this case, with the weird stuff we are doing with predictions... and the fact that
1711
- // // we already store the value... we simply don't need the check.
1712
- // return true;
1713
- return this.isUndefinedAtLock(readLock);
1714
- }
1715
- if (!values) {
1716
- // We have no record of any values on this path, so it must have never been committed!
1717
- return false;
1718
- }
1719
- let value = values.find(x => compareTime(x.time, time) === 0);
1720
- if (!value) {
1721
- // If it is golden, it was probably just removed, so assume it was accepted
1722
- // (nodes only ask for valid states of values they created, or that were
1723
- // just created, so this is fine)
1724
- if (time.time < now - MAX_CHANGE_AGE) {
1725
- return true;
1726
- }
1727
- return false;
1728
- }
1729
- return value.isValid;
1730
- }
1731
- private isUndefinedAtLock(lock: ReadLock): boolean {
1732
- let values = this.validStorage.get(lock.path);
1733
- let time = lock.startTime;
1734
- if (values) {
1735
- // Find the write at read time
1736
- let latestValue: WriteState | undefined;
1737
- for (let value of values) {
1738
- // Skip invalid
1739
- if (!value.isValid) continue;
1740
- // Skip writes AFTER our read (but not ON our read, as then we skip our read itself!)
1741
- if (compareTime(value.time, time) > 0) continue;
1742
- // Take the newest write that would affect us
1743
- if (!latestValue || compareTime(value.time, latestValue.time) > 0) {
1744
- latestValue = value;
1745
- }
1746
- }
1747
- if (latestValue && !latestValue.isTransparent) {
1748
- return false;
1749
- }
1750
- }
1751
- return true;
1752
- }
1753
-
1754
- private getWritesInRange(lock: ReadLock): WriteState[] | undefined {
1755
- let values = this.validStorage.get(lock.path);
1756
- if (!values) return undefined;
1757
- let times: WriteState[] | undefined;
1758
- for (let value of values) {
1759
- if (!value.isValid) continue;
1760
- // NOTE: This is the same comparison as in getValidStateChangedTriggers
1761
- if (compareTime(lock.startTime, value.time) < 0 && compareTime(value.time, lock.endTime) < 0) {
1762
- if (!times) times = [];
1763
- times.push(value);
1764
- }
1765
- }
1766
- return times;
1767
- }
1768
-
1769
- // Returns true if the valid state changed
1770
- public setWriteValidStateValue(pathValue: PathValue): boolean {
1771
- this.ensureGarbageCollectOldState();
1772
-
1773
- let time = pathValue.time;
1774
- // Clone time, so we aren't referencing any of the original object. This is very important for garbage collection.
1775
- time = { time: time.time, version: time.version, creatorId: time.creatorId, };
1776
- return this.setWriteValidState({
1777
- path: pathValue.path,
1778
- isValid: pathValue.valid || false,
1779
- time: time,
1780
- isTransparent: pathValue.isTransparent || false,
1781
- });
1782
- }
1783
- public setWriteValidState(write: WriteState, lockless?: boolean): boolean {
1784
- if (write.isValid) {
1785
- auditLog("ACCEPTING VALUE", { path: write.path, time: write.time.time, reason: write.reason });
1786
- } else {
1787
- auditLog("REJECTING VALUE", { path: write.path, time: write.time.time, reason: write.reason });
1788
- }
1789
- this.ensureGarbageCollectOldState();
1790
-
1791
- // If lockless then it starts valid, and always stays valid.
1792
- if (!lockless) {
1793
- let authorityValue = authorityStorage.getValueExactIgnoreInvalid(write.path, write.time);
1794
- if (authorityValue) {
1795
- authorityValue.valid = write.isValid;
1796
- // NOTE: I think it is fine now for us to set the valid state early? Hopefully...
1797
- // because it happens A LOT.
1798
- // if (!isCoreQuiet) {
1799
- // console.log(`Setting valid state of ${debugPathValuePath(authorityValue)} to ${write.isValid}`);
1800
- // }
1801
- } else {
1802
- // if (isNode()) {
1803
- // console.error(`Setting valid state of ${write.path}@${debugTime(write.time)} to ${write.isValid}, but the ValuePath was not found. If the ValuePath is found later, it might not have the valid state set correctly.`);
1804
- // }
1805
- }
1806
- }
1807
-
1808
- let path = write.path;
1809
- let time = write.time;
1810
- let isValid = write.isValid;
1811
- let values = this.validStorage.get(path);
1812
- if (!values) {
1813
- values = [];
1814
- this.validStorage.set(path, values);
1815
- }
1816
- // NOTE: We always have to search, even if lockless, so we can return false when the isValid state hasn't changed.
1817
- // Search from the end, as it is most likely time will have been recently added
1818
- let index = values.length - 1;
1819
- while (index >= 0) {
1820
- let diff = compareTime(values[index].time, time);
1821
- if (diff === 0) {
1822
- break;
1823
- }
1824
- index--;
1825
- }
1826
- if (index >= 0) {
1827
- if (values[index].isValid === isValid) {
1828
- return false;
1829
- }
1830
- values[index].isValid = isValid;
1831
- values[index].reason = write.reason;
1832
- } else {
1833
- values.push(write);
1834
- }
1835
- return true;
1836
- }
1837
-
1838
-
1839
- /** SOMEWHAT of a hack. Updates the inputs value states from cache. Needed, as it is possible for valid states
1840
- to be processed before PathValues. Which would cause the PathValues to have outdated valid states when received.
1841
- Valid states are always correct, and always updated (if they are sent at all), so we will use the latest valid
1842
- state instead of latest write state.
1843
- */
1844
- @measureFnc
1845
- public updateValidStatesFromCache(values: PathValue[]) {
1846
- for (let value of values) {
1847
- let states = this.validStorage.get(value.path);
1848
- if (!states) continue;
1849
- let state = states.find(x => compareTime(x.time, value.time) === 0);
1850
- if (!state) continue;
1851
- value.valid = state.isValid;
1852
- }
1853
- }
1854
-
1855
- // NOTE: Mutates the input PathValue.valid states to be the new states
1856
- // NOTE: Only mutates paths we are an authority on
1857
- // NOTE: Also sets up watches, so the valid states will update when their readLocks change
1858
- // (including talking to remote watchers, etc, etc)
1859
- @measureFnc
1860
- public computeValidStates(values: PathValue[], now: number, alreadyWatching?: "alreadyWatching"): WriteState[] {
1861
- // Calculate valid states
1862
- // - For things we are not an authority on, assumes their state is true.
1863
- // - If we AREN'T an authority, whomever sent us the values will send us new
1864
- // values if these become invalid.
1865
- // - Also, if we created the values, we will get sent valid states if
1866
- // they become invalid.
1867
- // - We don't need to watch locks for pathValues, as any valid states changes
1868
- // automatically impact any values that use them (we only need to watch locks
1869
- // for values we are computing).
1870
- // - For everything we ARE an authority on, computes them
1871
- // - Also sets up watches for computed values, so the valid states will stay up to date).
1872
-
1873
- let changes: WriteState[] = [];
1874
-
1875
- // If alreadyWatching... then these are triggers caused by a local lock watch. In which case,
1876
- // are must want to know if the values changed (as in, for call prediction).
1877
- if (!alreadyWatching) {
1878
- measureBlock(function computeValidStates_getSelfValues() {
1879
- let selfValues: PathValue[] = [];
1037
+ public getAllValues(config: {
1038
+ spec: AuthoritySpec;
1039
+ // Only returns values with a create time in this window
1040
+ startTime: number;
1041
+ endTime: number;
1042
+ // Use pathValueSerializer.deserialize(valueBuffers) on the output buffers
1043
+ }): PathValue[] {
1044
+ let { spec, startTime, endTime } = config;
1045
+ let result: PathValue[] = [];
1046
+ for (let [path, values] of this.values) {
1047
+ if (PathRouter.matchesAuthoritySpec(spec, path)) {
1880
1048
  for (let value of values) {
1881
- if (pathValueAuthority2.isSelfAuthority(value.path)) {
1882
- selfValues.push(value);
1883
- } else {
1884
- let changed = writeValidStorage.setWriteValidStateValue(value);
1885
- if (changed) {
1886
- changes.push({ path: value.path, isValid: value.valid || false, time: value.time, isTransparent: value.isTransparent || false });
1887
- }
1888
- }
1889
- }
1890
- values = selfValues;
1891
- });
1892
- }
1893
-
1894
- if (values.length === 0) return changes;
1895
-
1896
- // Watch the value locks, for all SELF VALUES
1897
- // (watches remote locks if necessary)
1898
- if (!alreadyWatching) {
1899
- lockWatcher.watchValueLocks(values);
1900
- }
1901
- let lockGroups = new Map<ReadLock[], PathValue[]>();
1902
- for (let value of values) {
1903
- if (value.lockCount === 0) {
1904
- validateLockCount(value);
1905
- let change: WriteState = {
1906
- path: value.path,
1907
- isValid: true,
1908
- time: value.time,
1909
- isTransparent: value.isTransparent || false
1910
- };
1911
- let changed = writeValidStorage.setWriteValidState(change, true);
1912
- if (changed) {
1913
- changes.push(change);
1914
- }
1915
- continue;
1916
- }
1917
- let lockGroup = lockGroups.get(value.locks);
1918
- if (!lockGroup) {
1919
- lockGroup = [];
1920
- lockGroups.set(value.locks, lockGroup);
1921
- }
1922
- lockGroup.push(value);
1923
- }
1924
- for (let [locks, valueGroup] of lockGroups) {
1925
- if (valueGroup.length === 0) continue;
1926
-
1927
- let rejected = false;
1928
- for (let lock of locks) {
1929
- if (!this.isLockValid(lock, now)) {
1930
- rejected = true;
1931
-
1932
- // This is good... unless it happens A LOT. Then the app needs to change (unless it
1933
- // is a game, or something with realtime competition, in which case this is probably unavoidable).
1934
- if ((!isCoreQuiet || !isNode()) && debugRejections || valueGroup.length > 0 && lock.endTime.version !== Number.MAX_SAFE_INTEGER && lock.startTime.version !== -2) {
1935
- if (!isNode()) {
1936
- debugger;
1937
- }
1938
- let timeToReject = now - valueGroup[0].time.time;
1939
- let message = `!!! VALUE REJECTED DUE TO USING MISSING / REJECTED READ!!!(rejected after ${timeToReject}ms at ${Date.now()})`;
1940
- message += `\n${debugTime(valueGroup[0].time)} (write)`;
1941
- message += `\n rejected as the server could not find: `;
1942
- if (lock.readIsTransparent) {
1943
- message += `\n${debugTime(lock.startTime)} to ${debugTime(lock.endTime)} ${getPathFromStr(lock.path).join(".")} `;
1944
- } else {
1945
- message += `\n${debugTime(lock.startTime)} ${getPathFromStr(lock.path).join(".")} `;
1946
- }
1947
- if (lock.readIsTransparent) {
1948
- message += `\n (read was undefined, so presumably a value exists which the writer missed)`;
1949
- }
1950
- message += `\nFull list of writes rejected: `;
1951
- for (let pathValue of valueGroup) {
1952
- message += `\n${debugPathValue(pathValue)}`;
1953
- }
1954
- console.log(red(message));
1955
-
1956
- if (debugRejections) {
1957
- debugbreak(2);
1958
- debugger;
1959
- this.isLockValid(lock, now);
1960
- debugger;
1961
- }
1962
- }
1963
- break;
1964
- }
1965
- let inRange = this.getWritesInRange(lock);
1966
- if (inRange?.length) {
1967
- // If all the offending writes are undefined (or invalid), then we can ignore the contention
1968
- if (lock.readIsTransparent && inRange.every(x => x.isTransparent || !x.isValid)) {
1969
- continue;
1970
- }
1971
- rejected = true;
1972
- // This is good... unless it happens A LOT. Then the app needs to change (unless it
1973
- // is a game, or something with realtime competition, in which case this is probably unavoidable).
1974
- if (
1975
- (!isCoreQuiet || !isNode())
1976
- // This special version indicates clientside prediction (which SHOULD be rejected).
1977
- && lock.endTime.version !== Number.MAX_SAFE_INTEGER
1978
- || debugRejections
1979
- ) {
1980
- let changed = valueGroup.filter(x => x.valid);
1981
- if (changed.length > 0) {
1982
- let timeToReject = now - changed[0].time.time;
1983
- let redMessage = `!!! LOCK CONTENTION FIXED VIA REJECTION OF VALUE!!!(rejected after ${timeToReject}ms)`;
1984
- for (let pathValue of changed) {
1985
- redMessage += `\n${debugPathValue(pathValue)}`;
1986
- }
1987
- redMessage += `\n (write rejected due to original read not noticing value at: ${lock.path})`;
1988
- redMessage += `\n (original read from: ${debugTime(lock.startTime)}`;
1989
- redMessage += `\n (at time: ${debugTime(lock.endTime)}`;
1990
- redMessage += `\n (current time: ${Date.now()})`;
1991
- for (let lockFailed of inRange) {
1992
- redMessage += `\n (conflict write at: ${debugTime(lockFailed.time)}`;
1993
- }
1994
- console.log(red(redMessage));
1995
- if (debugRejections) {
1996
- debugbreak(2);
1997
- debugger;
1998
- this.getWritesInRange(lock);
1999
- debugger;
2000
- }
2001
- }
2002
- }
2003
- break;
2004
- }
2005
- }
2006
- // NOTE: The if statement is equivalent to this one line, we just iterate so we can
2007
- // have better debug info, and rejection metrics tracking.
2008
- //values.forEach(value => value.valid = !rejected);
2009
- if (rejected) {
2010
- for (let pathValue of valueGroup) {
2011
- pathValue.valid = false;
2012
- rejections.value++;
2013
- }
2014
- }
2015
-
2016
- for (let value of valueGroup) {
2017
- let change: WriteState = {
2018
- path: value.path,
2019
- isValid: value.valid || false,
2020
- time: value.time,
2021
- isTransparent: value.isTransparent || false
2022
- };
2023
- let changed = writeValidStorage.setWriteValidState(change);
2024
- if (changed) {
2025
- changes.push(change);
2026
- }
2027
- }
2028
- }
2029
-
2030
- return changes;
2031
- }
2032
-
2033
- public deleteRemovedPath(path: string) {
2034
- this.validStorage.delete(path);
2035
- }
2036
-
2037
- private ensureGarbageCollectOldState = lazy(() => runInfinitePoll(MAX_CHANGE_AGE, () => {
2038
- // Clear all valid states after they are old enough. We will just tell anyone who asks that
2039
- // they are valid, because... the must be if someone still cares about them (as either they have been
2040
- // syncing for a long time, and so they must have never gotten an invalid message and it is too late
2041
- // for that to change now, or... they are new, which means they will either be given old valid values,
2042
- // or new values which may be invalid but are too new for us to garbage collect).
2043
- let garbageCollectTime = Date.now() - MAX_CHANGE_AGE;
2044
- for (let [key, values] of this.validStorage) {
2045
- values = values.filter(x => x.time.time > garbageCollectTime);
2046
- if (values.length === 0) {
2047
- this.validStorage.delete(key);
2048
- } else {
2049
- this.validStorage.set(key, values);
2050
- }
2051
- }
2052
- }));
2053
- }
2054
- export const writeValidStorage = new WriteValidStorage();
2055
-
2056
- let rejections = { value: 0 };
2057
- export function internalGetRejections(): { value: number } {
2058
- return rejections;
2059
- }
2060
- export function internalTestResetRejections() {
2061
- rejections.value = 0;
2062
- }
2063
-
2064
- class LockWatcher {
2065
- @measureFnc
2066
- public watchValueLocks(values: PathValue[]): void {
2067
- let { newRemoteLocks } = lockWatchDeduper.localWatchNewLocks({ values: values });
2068
- if (newRemoteLocks.size > 0) {
2069
- logErrors(this.watchRemoteLocks(newRemoteLocks));
2070
- }
2071
- }
2072
- private watchRemoteLocks = batchFunction(
2073
- { delay: "afterio" },
2074
- async (locksBatched: Set<ReadLock>[]) => {
2075
- let locks = new Set(locksBatched.flatMap(x => Array.from(x)));
2076
- let locksByAuthority = new Map<string, Set<ReadLock>>();
2077
- for (let lock of locks) {
2078
- if (pathValueAuthority2.isSelfAuthority(lock.path)) continue;
2079
-
2080
- let authorityId = await pathValueAuthority2.getSingleReadNodePromise(lock.path);
2081
- // If they don't trust us, we just have to assume the value we read is correct.
2082
- if (!await isTrustedByNode(authorityId)) continue;
2083
- let locks = locksByAuthority.get(authorityId);
2084
- if (!locks) {
2085
- locks = new Set();
2086
- locksByAuthority.set(authorityId, locks);
2087
- }
2088
- locks.add(lock);
2089
- }
2090
-
2091
- for (let [authorityId, locks] of locksByAuthority) {
2092
- this.watchLocksOnAuthority(authorityId, locks);
2093
- }
2094
- }
2095
- );
2096
- private watchLocksOnAuthority(authorityId: string, locks: Set<ReadLock>) {
2097
- if (!Array.from(locks).some(x => lockWatchDeduper.isLockStillWatched(x))) return;
2098
-
2099
- let alreadyReconnected = false;
2100
- const reconnectWatches = () => {
2101
- if (alreadyReconnected) return;
2102
- alreadyReconnected = true;
2103
- if (!Array.from(locks).some(x => lockWatchDeduper.isLockStillWatched(x))) return;
2104
- logErrors(this.watchRemoteLocks(locks));
2105
- };
2106
- this.onNextDisconnectList(authorityId).add(reconnectWatches);
2107
- setTimeout(() => {
2108
- this.onNextDisconnectList(authorityId).delete(reconnectWatches);
2109
- }, MAX_CHANGE_AGE * 2);
2110
-
2111
- let connection = PathValueController.nodes[authorityId].watchLockValid(
2112
- Array.from(locks.values())
2113
- );
2114
- logErrors(connection);
2115
- connection.catch(() => reconnectWatches());
2116
- }
2117
-
2118
- private onNextDisconnectList = cache((nodeId: string) => {
2119
- let callbacks = new Set<() => void>();
2120
- SocketFunction.onNextDisconnect(nodeId, () => {
2121
- this.onNextDisconnectList.clear(nodeId);
2122
- for (let callback of callbacks) {
2123
- callback();
2124
- }
2125
- });
2126
- return callbacks;
2127
- });
2128
- }
2129
- export const lockWatcher = new LockWatcher();
2130
-
2131
- class LockToCallbackLookup {
2132
- // time.creatorId => time => callbacks
2133
- private validWatchers = registerResource("paths|validWatchers", new Map<number, Map<number, Set<WriteCallback>>>());
2134
-
2135
- // path => sorted by endTime
2136
- private validRangeWatchers = registerResource("paths|validRangeWatchers", new Map<string, {
2137
- startTime: Time;
2138
- endTime: Time;
2139
- // Called back if there is a valid value between startTime (exclusive) and endTime (exclusive)
2140
- callback: WriteCallback;
2141
- }[]>());
2142
-
2143
- /** Watchs both the startTime, and the range */
2144
- public watchLock(lock: ReadLock, callback: WriteCallback) {
2145
- this.ensureGarbageCollectLoop();
2146
- // Add valid watcher
2147
- if (lock.endTime.time > 0) {
2148
- const time = lock.startTime;
2149
- let maps = this.validWatchers.get(time.creatorId);
2150
- if (!maps) {
2151
- maps = new Map();
2152
- this.validWatchers.set(time.creatorId, maps);
2153
- }
2154
- let callbacksList = maps.get(time.time);
2155
- if (!callbacksList) {
2156
- callbacksList = new Set();
2157
- maps.set(time.time, callbacksList);
2158
- }
2159
- callbacksList.add(callback);
2160
- }
2161
- // Add valid range watcher
2162
- if (compareTime(lock.startTime, lock.endTime) !== 0) {
2163
- let { path, startTime, endTime } = lock;
2164
- let watchers = this.validRangeWatchers.get(path);
2165
- if (!watchers) {
2166
- watchers = [];
2167
- this.validRangeWatchers.set(path, watchers);
2168
- }
2169
- // Find the index to insert into, maintaining a sort by time
2170
- let index = -1;
2171
- for (let i = watchers.length - 1; i >= 0; i--) {
2172
- if (watchers[i].endTime.time < endTime.time) {
2173
- index = i + 1;
2174
- break;
2175
- }
2176
- }
2177
- if (index === -1) {
2178
- index = watchers.length;
2179
- }
2180
- watchers.splice(index, 0, { startTime, endTime, callback });
2181
- }
2182
- }
2183
-
2184
- public garbageCollectOldWatchers() {
2185
- const disposeTime = Date.now() - MAX_CHANGE_AGE * 2;
2186
-
2187
- for (let [path, times] of this.validRangeWatchers) {
2188
- let expiredIndex = times.findIndex(x => x.endTime.time < disposeTime);
2189
- if (expiredIndex >= 0) {
2190
- times.splice(expiredIndex, times.length - expiredIndex);
2191
- }
2192
- }
2193
-
2194
- for (let [key, maps] of this.validWatchers) {
2195
- for (let time of maps.keys()) {
2196
- if (time < disposeTime) {
2197
- maps.delete(time);
2198
- if (maps.size === 0) {
2199
- this.validWatchers.delete(key);
1049
+ let time = value.time.time;
1050
+ if (startTime <= time && time <= endTime) {
1051
+ result.push(value);
2200
1052
  }
2201
1053
  }
2202
1054
  }
2203
1055
  }
2204
-
2205
- for (let [path, watchers] of this.validRangeWatchers) {
2206
- for (let i = watchers.length - 1; i >= 0; i--) {
2207
- let watcher = watchers[i];
2208
- if (watcher.endTime.time < disposeTime) {
2209
- watchers.splice(i, 1);
2210
- }
2211
- }
2212
- if (watchers.length === 0) {
2213
- this.validRangeWatchers.delete(path);
2214
- }
2215
- }
1056
+ return result;
2216
1057
  }
2217
-
2218
- public getValidStateChangedTriggers(
2219
- remoteValue: WriteState
2220
- ): WriteCallback[] {
2221
- let allCallbacks: WriteCallback[] = [];
2222
- let validMaps = this.validWatchers.get(remoteValue.time.creatorId);
2223
- if (validMaps) {
2224
- let callbacks = validMaps.get(remoteValue.time.time);
2225
- if (callbacks) {
2226
- for (let callbackList of callbacks) {
2227
- allCallbacks.push(callbackList);
2228
- }
2229
- }
2230
- }
2231
- let rangeWatchers = this.validRangeWatchers.get(remoteValue.path);
2232
- if (rangeWatchers) {
2233
- for (let watcher of rangeWatchers) {
2234
- if (
2235
- // NOTE: This is the same comparison as in getWritesInRange
2236
- // If the time is > startTime (exclusive, as startTime MUST exist, as we depend on it)
2237
- compareTime(watcher.startTime, remoteValue.time) < 0
2238
- // And < endTime (exclusive, as endTime is assumed to exist, as it is probably when we are writing!)
2239
- && compareTime(remoteValue.time, watcher.endTime) < 0
2240
- ) {
2241
- allCallbacks.push(watcher.callback);
2242
- }
2243
- }
2244
- }
2245
- return allCallbacks;
2246
- }
2247
-
2248
- private ensureGarbageCollectLoop = lazy(() => {
2249
- runInfinitePoll(MAX_CHANGE_AGE, () => lockToCallback.garbageCollectOldWatchers());
2250
- });
2251
-
2252
-
2253
1058
  }
2254
- export const lockToCallback = new LockToCallbackLookup();
2255
-
2256
- // NOTE: We assume there will be no equivalent but !== PathValues. We also assume
2257
- // there will be no equivalent but !== Locks. Fair assumptions, which save so
2258
- // much time it is probably worth maintaining them. AND, if they are invalidated,
2259
- // the only penalty is a memory leak, which isn't so bad (arguably having identical
2260
- // values with difference instances is a memory leak too).
2261
- class LockWatchDeduper {
2262
- private watchedRemoteLocks = registerResource("paths|watchedRemoteLocks", new Map<ReadLock[], Set<PathValue>>());
2263
- private watchedLockFlat = registerResource("paths|watchedLockFlat", new Set<ReadLock>());
2264
-
2265
- /** Calls validWatcher with any new readLocks, using the values as the callback.
2266
- * SHOULD ONLY BE USED IF YOU ARE WATCHING THE LOCKS, aka, if you are
2267
- * the authority on the values. Otherwise, you are NOT watching readLocks,
2268
- * you are watching the valid state, via the actual authority of the paths.
2269
- */
2270
- public localWatchNewLocks(config: { values: PathValue[]; }): {
2271
- newRemoteLocks: Set<ReadLock>;
2272
- } {
2273
- this.ensureLockCleanupLoop();
2274
-
2275
- let byLock = new Map<ReadLock[], PathValue[]>();
2276
- for (let value of config.values) {
2277
- validateLockCount(value);
2278
- if (value.lockCount === 0) continue;
2279
- let values = byLock.get(value.locks);
2280
- if (!values) {
2281
- values = [];
2282
- byLock.set(value.locks, values);
2283
- }
2284
- values.push(value);
2285
- }
2286
-
2287
- let newLocks: ReadLock[] = [];
2288
- for (let value of config.values) {
2289
- validateLockCount(value);
2290
- if (value.lockCount === 0) continue;
2291
- let values = this.watchedRemoteLocks.get(value.locks);
2292
- if (!values) {
2293
- values = new Set();
2294
- this.watchedRemoteLocks.set(value.locks, values);
2295
- newLocks.push(...value.locks);
2296
- }
2297
- values.add(value);
2298
- }
2299
- for (let locks of byLock.keys()) {
2300
- for (let lock of locks) {
2301
- this.watchedLockFlat.add(lock);
2302
- }
2303
- }
2304
- let newRemoteLocks = new Set<ReadLock>();
2305
-
2306
- for (let [locks, values] of byLock) {
2307
- for (let lock of locks) {
2308
- // NOTE: We watch LOCAL_DOMAIN paths, as they might depend on a remote value, which is invalidated.
2309
- // This can happen for routing, where you click something like "go to main node", before
2310
- // the remote node list is loaded. You will go to the wrong page, BUT, once the remote node list
2311
- // is loaded, it should reject (and then, ideally we rerun the function clientside on detection
2312
- // of the rejection).
2313
-
2314
- if (!pathValueAuthority2.isSelfAuthority(lock.path)) {
2315
- newRemoteLocks.add(lock);
2316
- }
2317
- // NOTE: I BELIEVE this is valid, even if we don't own the locks. We might have some
2318
- // unnecessary cascading, but... if we depended on a value we will have it, and
2319
- // if we are missing any range lock conflicts we will know about them due to
2320
- // the remote locks. AND, any values we have are synced, so we don't need to worry about
2321
- // any range lock conflicts themselves being rejected, missing that rejection, and so
2322
- // incorrectly invalidating valid values that we just received.
2323
- lockToCallback.watchLock(lock, values);
2324
- }
2325
- }
2326
-
2327
- return { newRemoteLocks };
2328
- }
2329
-
2330
- private ensureLockCleanupLoop = lazy(() => {
2331
- runInfinitePoll(MAX_CHANGE_AGE, () => this.cleanupDeadLocks());
2332
- });
1059
+ export const authorityStorage = new AuthorityPathValueStorage();
1060
+ export const values = authorityStorage;
1061
+ export const pathValues = authorityStorage;
2333
1062
 
2334
- @measureFnc
2335
- private cleanupDeadLocks() {
2336
- let deadTime = Date.now() - MAX_CHANGE_AGE;
2337
- for (let [locks, values] of this.watchedRemoteLocks) {
2338
- let isDead = true;
2339
- for (let value of values) {
2340
- if (value.time.time > deadTime) {
2341
- isDead = false;
2342
- break;
2343
- }
2344
- }
2345
- if (isDead) {
2346
- for (let lock of locks) {
2347
- this.watchedLockFlat.delete(lock);
2348
- }
2349
- this.watchedRemoteLocks.delete(locks);
2350
- }
2351
- }
2352
- }
2353
1063
 
2354
- public isLockStillWatched(lock: ReadLock): boolean {
2355
- return this.watchedLockFlat.has(lock);
2356
- }
2357
- }
2358
- export const lockWatchDeduper = new LockWatchDeduper();
2359
1064
 
2360
1065
  // Async import, as we are not allowed synchronous imports of values in
2361
1066
  // higher tiers than us. We can't expose a callback, as nothing else
2362
1067
  // can trigger the import of memoryValueAudit for all clients as consistently
2363
1068
  // as the core.
2364
1069
  setImmediate(() => {
2365
- //logErrors(import("../5-diagnostics/memoryValueAudit").then(x => x.startMemoryAuditLoop()));
2366
- //logErrors(import("../5-diagnostics/diskValueAudit").then(x => x.startDiskAuditLoop()));
2367
1070
  logErrors(import("../5-diagnostics/synchronousLagTracking").then(x => x.trackSynchronousLag()));
2368
1071
  });
2369
1072