querysub 0.461.0 → 0.463.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 (95) hide show
  1. package/package.json +1 -1
  2. package/src/-d-trust/NetworkTrust2.ts +1 -1
  3. package/src/0-path-value-core/LockWatcher2.ts +2 -5
  4. package/src/0-path-value-core/PathRouter.ts +2 -3
  5. package/src/0-path-value-core/PathRouterConstants.ts +4 -0
  6. package/src/0-path-value-core/PathValueCommitter.ts +0 -1
  7. package/src/0-path-value-core/PathValueController.ts +1 -1
  8. package/src/0-path-value-core/PathWatcher.ts +14 -5
  9. package/src/0-path-value-core/ValidStateComputer.ts +234 -86
  10. package/src/0-path-value-core/pathValueCore.ts +57 -78
  11. package/src/1-path-client/RemoteWatcher.ts +4 -3
  12. package/src/1-path-client/pathValueClientWatcher.ts +28 -2
  13. package/src/2-proxy/PathValueProxyWatcher.ts +29 -24
  14. package/src/2-proxy/TransactionDelayer.ts +44 -22
  15. package/src/2-proxy/archiveMoveHarness.ts +2 -2
  16. package/src/3-path-functions/PathFunctionRunner.ts +30 -22
  17. package/src/3-path-functions/PathFunctionRunnerMain.ts +1 -3
  18. package/src/4-deploy/deployFunctions.ts +1 -1
  19. package/src/4-deploy/deployMain.ts +1 -1
  20. package/src/4-deploy/edgeClientWatcher.tsx +1 -1
  21. package/src/4-deploy/edgeNodes.ts +1 -1
  22. package/src/4-dom/qreactTest.tsx +1 -1
  23. package/src/4-querysub/Querysub.ts +8 -9
  24. package/src/4-querysub/QuerysubController.ts +19 -1
  25. package/src/4-querysub/permissions.ts +1 -1
  26. package/src/4-querysub/predictionQueue.tsx +1 -1
  27. package/src/4-querysub/querysubPrediction.ts +25 -12
  28. package/src/5-diagnostics/GenericFormat.tsx +1 -1
  29. package/src/5-diagnostics/qreactDebug.tsx +2 -2
  30. package/src/archiveapps/archiveGCEntry.tsx +1 -1
  31. package/src/archiveapps/archiveJoinEntry.ts +3 -3
  32. package/src/config.ts +5 -1
  33. package/src/config2.ts +9 -7
  34. package/src/deployManager/components/CommitModal.tsx +1 -1
  35. package/src/deployManager/components/DeployProgressView.tsx +1 -1
  36. package/src/deployManager/components/MachineDetailPage.tsx +1 -1
  37. package/src/deployManager/components/MachinesListPage.tsx +1 -1
  38. package/src/deployManager/components/ServiceDetailPage.tsx +1 -1
  39. package/src/deployManager/components/ServicesListPage.tsx +1 -1
  40. package/src/deployManager/components/Tools.tsx +1 -1
  41. package/src/deployManager/machineApplyMainCode.ts +3 -3
  42. package/src/deployManager/machineController.ts +1 -1
  43. package/src/deployManager/machineSchema.ts +1 -1
  44. package/src/deployManager/setupMachineMain.ts +1 -1
  45. package/src/diagnostics/FunctionCallInfoState.ts +6 -3
  46. package/src/diagnostics/MachineThreadInfo.tsx +1 -1
  47. package/src/diagnostics/NodeViewer.tsx +2 -1
  48. package/src/diagnostics/StatWarning.tsx +56 -0
  49. package/src/diagnostics/StatsHeader.tsx +248 -0
  50. package/src/diagnostics/StatsOverrides.ts +50 -0
  51. package/src/diagnostics/SyncTestPage.tsx +3 -3
  52. package/src/diagnostics/TimeDebug.tsx +1 -1
  53. package/src/diagnostics/debugger/mcp-server.ts +1 -1
  54. package/src/diagnostics/grossStats/GrossStatsPage.tsx +1 -1
  55. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +1 -1
  56. package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts +1 -1
  57. package/src/diagnostics/logs/TimeRangeSelector.tsx +1 -1
  58. package/src/diagnostics/logs/errorNotifications2/ErrorNotificationPage.tsx +1 -1
  59. package/src/diagnostics/logs/errorNotifications2/ErrorWarning.tsx +0 -1
  60. package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +1 -1
  61. package/src/diagnostics/logs/errorNotifications2/errorWatchEntry.ts +1 -1
  62. package/src/diagnostics/logs/errorNotifications2/errorWatcher.ts +1 -1
  63. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +1 -1
  64. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +1 -1
  65. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +1 -1
  66. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +1 -1
  67. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +1 -1
  68. package/src/diagnostics/managementPages.tsx +4 -9
  69. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +1 -1
  70. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +1 -1
  71. package/src/diagnostics/misc-pages/DNSPage.tsx +1 -1
  72. package/src/diagnostics/pathAuditer.ts +6 -6
  73. package/src/diagnostics/statsDefinitions.tsx +253 -0
  74. package/src/functional/throttleRender.ts +1 -1
  75. package/src/library-components/AspectSizedComponent.tsx +1 -1
  76. package/src/library-components/Button.tsx +1 -1
  77. package/src/library-components/ButtonSelector.tsx +1 -1
  78. package/src/library-components/DropdownCustom.tsx +1 -1
  79. package/src/library-components/DropdownSelector.tsx +1 -1
  80. package/src/library-components/Histogram.tsx +1 -1
  81. package/src/library-components/InlinePopup.tsx +1 -1
  82. package/src/library-components/LazyComponent.tsx +5 -1
  83. package/src/library-components/StickyBottomScroll.tsx +1 -1
  84. package/src/library-components/TypedConfigEditor.tsx +1 -1
  85. package/src/library-components/URLParam.ts +5 -9
  86. package/src/library-components/drag.ts +1 -1
  87. package/src/misc/formatJSX.tsx +1 -1
  88. package/src/user-implementation/userData.ts +1 -1
  89. package/test2.ts +1 -1
  90. package/testEntry2.ts +1 -1
  91. package/valid.md +205 -0
  92. package/src/deployManager/LaunchTrackingHeader.tsx +0 -65
  93. package/src/diagnostics/FunctionCallInfo.tsx +0 -141
  94. package/src/diagnostics/PathDistributionInfo.tsx +0 -110
  95. package/src/diagnostics/ValuePathWarning.tsx +0 -68
@@ -11,7 +11,7 @@ import { delay, runInfinitePoll } from "socket-function/src/batching";
11
11
  import { registerMapArrayResource, registerResource } from "../diagnostics/trackResources";
12
12
  import { isNode, isNodeTrue, sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
13
13
  import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
14
- import { AuthoritySpec, PathRouter } from "./PathRouter";
14
+ import { AuthoritySpec, LOCAL_DOMAIN_PATH, PathRouter } from "./PathRouter";
15
15
  import { formatTime } from "socket-function/src/formatting/format";
16
16
  import { isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
17
17
  import { PromiseRace } from "socket-function/src/promiseRace";
@@ -28,6 +28,7 @@ import { isDiskAudit } from "../config";
28
28
  import { removeRange } from "../rangeMath";
29
29
  import { remoteWatcher } from "../1-path-client/RemoteWatcher";
30
30
  import { sha256 } from "js-sha256";
31
+ import { evaluateValidStates, isServer } from "../config2";
31
32
 
32
33
  setImmediate(async () => {
33
34
  // Import everything will dynamically import, so the client side can tell that it's required.
@@ -65,6 +66,11 @@ export const MAX_ACCEPTED_CHANGE_AGE = 1000 * 30;
65
66
  */
66
67
  export const MAX_CHANGE_AGE = MAX_ACCEPTED_CHANGE_AGE * 2;
67
68
 
69
+ /** After this we start considering reading values and GCing them. Before this we
70
+ * just merge the values, not really processing them.
71
+ */
72
+ export const VALUE_GC_THRESHOLD = MAX_CHANGE_AGE * 2;
73
+
68
74
  /** Extra time we keep clientside prediction rejections for, to give us time to receive the actual values. */
69
75
  export const CLIENTSIDE_PREDICT_LEEWAY = 500;
70
76
 
@@ -76,10 +82,7 @@ export const CLIENTSIDE_PREDICT_LEEWAY = 500;
76
82
  */
77
83
  export const ARCHIVE_FLUSH_LIMIT = Math.max(MAX_CHANGE_AGE * 10, timeInMinute * 60);
78
84
 
79
- /** After this we start considering reading values and GCing them. Before this we
80
- * just merge the values, not really processing them.
81
- */
82
- export const VALUE_GC_THRESHOLD = ARCHIVE_FLUSH_LIMIT * 3;
85
+ export const ARCHIVE_STABLE_THRESHOLD = ARCHIVE_FLUSH_LIMIT * 2;
83
86
 
84
87
  // Only start using ARCHIVE_FLUSH_LIMIT after this time.
85
88
  // Before this date, we moves some files around, so their timestamps are too old.
@@ -96,6 +99,12 @@ export const MAX_ARCHIVE_PROPAGATION_DELAY = 1000 * 10;
96
99
  /** The time we wait until we cleanup undefined values from memory. */
97
100
  export const UNDEFINED_MEMORY_CLEANUP_DELAY = MAX_CHANGE_AGE * 2;
98
101
 
102
+ /** If the rejection is that we're missing a value, We defer it for this window. */
103
+ export const DEFER_LOCK_WINDOW = 2500;
104
+
105
+ /** Hold predictions a little bit longer so we can wait for multiple parts to come in. We could be smarter about this, but there's not really any downside in waiting a few seconds with the prediction. Predictions really shouldn't be wrong. And if they are, this actually makes it easier to diagnose when they are wrong and then fix the code so they're not wrong. */
106
+ export const PREDICT_HOLD_TIME = 2500;
107
+
99
108
  export const ARCHIVE_LOOP_DELAY = MAX_CHANGE_AGE * 2;
100
109
 
101
110
  export const MIN_WAIT_TIME_UNTIL_DISK_FLUSH = MAX_CHANGE_AGE * 2;
@@ -129,6 +138,8 @@ export const STARTUP_CUTOFF_TIME = THREAD_START_TIME + timeInSecond * 30;
129
138
  */
130
139
  export const MAX_ACCEPTED_AUTHORITY_STARTUP_TIME = timeInMinute * 30;
131
140
 
141
+ export const MISSING_TRANSACTION_PART_TIMEOUT = 15000;
142
+
132
143
  export const predictionLockVersion = -2;
133
144
  export function isOurPrediction(value: PathValue) {
134
145
  return value.locks.length === 1 && value.locks[0].startTime.version === predictionLockVersion;
@@ -167,12 +178,6 @@ export function timeMinusEpsilon(time: Time) {
167
178
  return { time: time.time, version: time.version, creatorId: minusEpsilon(time.creatorId) };
168
179
  }
169
180
 
170
- function validateLockCount(value: PathValue) {
171
- if (value.lockCount === 0 && value.locks.length > 0) {
172
- throw new Error(`lockCount is 0, but locks are present (there are ${value.locks.length}), the value is invalid, lockCount MUST be set correctly`);
173
- }
174
- }
175
-
176
181
  // The startTime MUST have a valid value. For `startTime < other.time < endTime`
177
182
  // them there CANNOT be a valid value (if there is, it means there was contention,
178
183
  // and the lock is rejected, invalidating the write that uses it).
@@ -189,11 +194,10 @@ function validateLockCount(value: PathValue) {
189
194
  export type ReadLock = {
190
195
  path: string;
191
196
  // Exclusive (for ranges), often === the time of the value read
192
- // === (for single values), often === the time of the value read, but can be any value,
193
- // as long as it exists. UNLESS readIsTransparent, then it it must either not exist,
194
- // or be a transparent write.
197
+ // === (for single values), often === the time of the value read, but can be any value (although the path is ReadLock.path), as long as it exists. UNLESS readIsTransparent, then this is only used for the range check.
195
198
  startTime: Time;
196
199
  // Exclusive (for ranges), often === the time we are writing at
200
+ // SHOULD uniquely identify the ReadLock array. If for some reason, you want to have different read lock arrays, you're going to need to have a different end time. It's probably best to never have different read lock arrays, and you could perhaps have certain values not participate in the locks, that's fine, but you shouldn't have them participate in different locks.
197
201
  endTime: Time;
198
202
  // We don't store the ENTIRE value because that would result in sending the entire
199
203
  // program state per write (although I guess if you only read numbers ReadLock
@@ -243,8 +247,7 @@ export type PathValue = {
243
247
  */
244
248
  locks: ReadLock[];
245
249
 
246
- /** Should be set EVERY time we have locks. IF lockCount === 0, then we know there are no locks
247
- * (this is preserved through serialization, even when locks aren't).
250
+ /** Should be set EVERY time we have locks. IF lockCount === 0, then we know there are no locks.
248
251
  */
249
252
  lockCount: number;
250
253
 
@@ -294,7 +297,9 @@ export type PathValue = {
294
297
  updateCount?: number;
295
298
 
296
299
  /** This is the hashes of all the paths that were written at the same time, as in that have the same time as this path value.
297
- - By using this, you can determine if you've only partially read a transaction by looking at the paths which you have read and comparing them against these path values. And if you see that some of them have a time equal to our time, but some of them have a time before our time, it means that you are missing part of our transaction. This allows the client to wait for the rest of the transaction to be received. */
300
+ - By using this, you can determine if you've only partially read a transaction by looking at the paths which you have read and comparing them against these path values. And if you see that some of them have a time equal to our time, but some of them have a time before our time, it means that you are missing part of our transaction. This allows the client to wait for the rest of the transaction to be received.
301
+ IMPORTANT! All of these paths should have === locks. If we ever want locks to vary... make sure to NOT include those in the same transactionPaths.
302
+ */
298
303
  transactionPaths?: string[];
299
304
 
300
305
  // #endregion
@@ -389,7 +394,7 @@ export function debugPathValuePath(pathValue: { time: Time; path: string }): str
389
394
  return `(${debugTime(pathValue.time)}) ${pathValue.path}`;
390
395
  }
391
396
  export function debugPathValue(pathValue: PathValue, write?: boolean): string {
392
- return `${debugPathValuePath(pathValue)} ${write ? "=" : "==="} ${ellipsisMiddle(String(pathValueSerializer.getPathValue(pathValue)), 200)}`;
397
+ return `${debugPathValuePath(pathValue)} ${write ? "=" : "==="} ${ellipsisMiddle(String(pathValueSerializer.getPathValue(pathValue)), 200)}${!pathValue.valid && " (invalid)" || ""}`;
393
398
  }
394
399
  export function debugTime(time: Time): string {
395
400
  return `${time.time}[${time.version}]@${time.creatorId.toString().slice(4, 12)}`;
@@ -430,6 +435,8 @@ class AuthorityPathValueStorage {
430
435
  >());
431
436
 
432
437
  public DEBUG_UNWATCH = false;
438
+ /** Causes us to leak a lot, but useful for debugging */
439
+ public DEBUG_NEVER_GC = false;
433
440
 
434
441
  public overrides = new Set<Map<string, PathValue>>();
435
442
 
@@ -451,10 +458,10 @@ class AuthorityPathValueStorage {
451
458
  // with time > Date.now(), in the case that our clock is behind the server.
452
459
 
453
460
  public getValue(path: string): unknown {
454
- return pathValueSerializer.getPathValue(this.getValueAtTime(path));
461
+ return pathValueSerializer.getPathValue(this.getValueAtOrBeforeTime(path));
455
462
  }
456
- public getValueExact(path: string, time: Time): PathValue | undefined {
457
- let value = this.getValueAtTime(path, time, true);
463
+ public getValueExactMaybeRejected(path: string, time: Time): PathValue | undefined {
464
+ let value = this.getValueAtOrBeforeTime(path, time, true);
458
465
  if (value && compareTime(value.time, time) === 0) {
459
466
  return value;
460
467
  }
@@ -462,7 +469,7 @@ class AuthorityPathValueStorage {
462
469
  }
463
470
 
464
471
  /** time === undefined gets the latest value */
465
- public getValueAtTime(
472
+ public getValueAtOrBeforeTime(
466
473
  path: string,
467
474
  // Include all values with other.time <= time, returning the one with the highest time
468
475
  // (either that is valid, or, if readInvalid is true, just the highest time).
@@ -504,7 +511,7 @@ class AuthorityPathValueStorage {
504
511
  return this.getValueOrEpochIfSynced(path);
505
512
  }
506
513
  public getValueBeforeTime(path: string, time: Time): PathValue | undefined {
507
- return this.getValueAtTime(path, timeMinusEpsilon(time));
514
+ return this.getValueAtOrBeforeTime(path, timeMinusEpsilon(time));
508
515
  }
509
516
  public getValueOrEpochIfSynced(path: string): PathValue | undefined {
510
517
  if (this.isSynced(path)) {
@@ -558,6 +565,7 @@ class AuthorityPathValueStorage {
558
565
  * NOOPS if we are the authority of the path.
559
566
  */
560
567
  public destroyPath(path: string) {
568
+ if (this.DEBUG_NEVER_GC) return;
561
569
  // If we are the authority we:
562
570
  // 1) Need to maintain the values for other nodes
563
571
  // 2) Don't need to worry about the values getting out of date,
@@ -767,42 +775,34 @@ class AuthorityPathValueStorage {
767
775
 
768
776
  // Store in global lookup
769
777
  {
770
- let specialGoldenCase = false;
771
778
  let values = this.values.get(value.path);
772
779
  if (!values) {
773
780
  values = [];
774
781
  this.values.set(value.path, values);
775
782
  this.addParentPath(value.path);
776
783
  }
784
+
785
+ // IF a local path, we only store the latest.
786
+ // NOTE: If !evaluateValidStates(), We could probably get away with not storing the history. However, it is sometimes useful to have the history client-side as it can help us show a more accurate UI state when rejections happen (instead of temporarily reverting to show undefined)
787
+ if (value.path.startsWith(LOCAL_DOMAIN_PATH)) {
788
+ values[0] = value;
789
+ continue;
790
+ }
791
+
777
792
  let insertIndex = values.findIndex(x => compareTime(x.time, value.time) <= 0);
778
793
  if (insertIndex < 0) {
779
794
  values.push(value);
780
795
  } else {
781
796
  let prev = values[insertIndex];
782
797
  if (prev && compareTime(prev.time, value.time) === 0) {
783
- // 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.
784
- values[insertIndex] = value;
785
- // Just update the valid state (we likely aren't an authority on the path anyways)
786
- //prev.valid = value.valid;
798
+ // IMPORTANT! We HAVE to update the valid state, otherwise our archive values won't have the correct valid state, as they can't check storage, as we evict values from storage!
799
+ // @ts-expect-error
800
+ values[insertIndex].valid = value.valid;
787
801
  } else {
788
- // Special case values that always have no locks, by just setting the value, and never splicing
789
- if (insertIndex === 0 && value.lockCount === 0 && values.length <= 1) {
790
- validateLockCount(value);
791
- if (!isCoreQuiet && !doNotArchive) {
792
- if (values.length === 1) {
793
- console.log(`Clobbering ${debugPathValuePath(values[0])} with ${debugPathValuePath(value)}`);
794
- }
795
- }
796
- specialGoldenCase = true;
797
- values[0] = value;
798
- } else {
799
- values.splice(insertIndex, 0, value);
800
- }
802
+ values.splice(insertIndex, 0, value);
801
803
  }
802
804
  }
803
- if (!specialGoldenCase) {
804
- this.clearRedundantOldValues(values, now, doNotArchive);
805
- }
805
+ this.clearRedundantOldValues(values, now, doNotArchive);
806
806
  }
807
807
  }
808
808
 
@@ -898,34 +898,19 @@ class AuthorityPathValueStorage {
898
898
  // we will call this anyways).
899
899
  // NOTE: values are sorted sorted by -time (so newest are first)
900
900
  public clearRedundantOldValues(values: PathValue[], now: number, doNotArchive?: boolean) {
901
- // NOTE: We can also delete golden rejections BEFORE the valid golden value. But...
902
- // that is less important, as there are likely to be few rejected values, so this
903
- // is really just designed for removing older golden values (which are probably valid).
901
+ if (authorityStorage.DEBUG_NEVER_GC) return;
902
+
903
+ // IMPORTANT! We actually can't clear as many values as I thought we could because a value might be used in a lock, even though itself it has no locks.
904
904
 
905
- // NOTE: We clear values as soon as we can to make FunctionRunners run faster, as any database
906
- // with many writes will almost certainly have many FunctionRunner calls, which all need to synchronize
907
- // as much history as we have. So... the less history, we faster FunctionRunners can sync. AND syncing
908
- // data is the true bottleneck, as it requires network traffic, vs this, which is just manipulating
909
- // some values in memory...
905
+ // NOTE: We do want to clear as many values as possible because on initial sync we have to send the full history.
906
+ // AND, We do an initial sync every time there's a rejection, to EVERY watcher on that path, so... we REALLY want the history to be small... But if it's too small, then we won't be able to do rejections properly.
910
907
 
911
- let goldenTime = now - MAX_CHANGE_AGE;
908
+ let goldenTime = now - VALUE_GC_THRESHOLD;
912
909
  // NOTE: Values with no locks are implicitly golden, as they can't be rejected
913
- let firstGoldenIndex = values.findIndex(x => x.valid && (x.time.time < goldenTime || x.lockCount === 0));
910
+ let firstGoldenIndex = values.findIndex(x => x.valid && (x.time.time < goldenTime));
914
911
  if (firstGoldenIndex < 0) return;
915
912
 
916
- // Special case, everything is golden, and the latest value is undefined, and is behind the GC
917
- // point, then delete everything, so constantly changing keys doesn't result in us leaking memory forever.
918
- if (
919
- firstGoldenIndex === 0
920
- && values[0].canGCValue
921
- && values[0].time.time < now - VALUE_GC_THRESHOLD
922
- ) {
923
- if (!isCoreQuiet && !doNotArchive) {
924
- console.log(`GCing ALL ${debugPathValuePath(values[0])}`);
925
- for (let value of values) {
926
- auditLog("GCing", { path: value.path });
927
- }
928
- }
913
+ if (firstGoldenIndex === 0 && values[0].canGCValue) {
929
914
  // Mark the value as synced though, as synced means we have received values for this path,
930
915
  // and while the values are undefined and so we are deleting them... we have definitely
931
916
  // received values!
@@ -943,27 +928,19 @@ class AuthorityPathValueStorage {
943
928
 
944
929
  if (firstGoldenIndex === 0) {
945
930
  // Remove everything but the latest value
946
- let removed = values.splice(1, values.length - 1);
947
- if (!isCoreQuiet && !doNotArchive) {
948
- console.log(`GCing everything older than ${debugPathValuePath(values[0])}`);
949
- for (let value of removed) {
950
- auditLog("GCing", { path: value.path });
951
- }
952
- }
931
+ values.splice(1, values.length - 1);
953
932
  } else {
954
933
  // Delete everything that is golden, except for the most recent VALID value.
955
934
  if (firstGoldenIndex >= 0 && values.length > firstGoldenIndex) {
956
935
  for (let i = values.length - 1; i > firstGoldenIndex; i--) {
957
- let gced = values.pop();
958
- if (!isCoreQuiet && !doNotArchive && gced) {
959
- auditLog("GCing", { path: gced.path });
960
- }
936
+ values.pop();
961
937
  }
962
938
  }
963
939
  }
964
940
  }
965
941
 
966
942
  public removePathFromStorage(path: string, reason: string) {
943
+ if (authorityStorage.DEBUG_NEVER_GC) return;
967
944
  let values = this.values.get(path);
968
945
  if (values?.length) {
969
946
  this.lastDeleteAt.set(path, values[0].time);
@@ -989,6 +966,7 @@ class AuthorityPathValueStorage {
989
966
 
990
967
  private possiblyUndefinedPaths = lazy((): { value: Set<string> } => {
991
968
  let pathsToCheckPointer = { value: new Set<string>() };
969
+ if (authorityStorage.DEBUG_NEVER_GC) return pathsToCheckPointer;
992
970
 
993
971
  runInfinitePoll(UNDEFINED_MEMORY_CLEANUP_DELAY, async () => {
994
972
  const { pathWatcher } = await import("./PathWatcher");
@@ -1035,6 +1013,7 @@ class AuthorityPathValueStorage {
1035
1013
 
1036
1014
  private eventPaths = lazy((): { value: Set<string> } => {
1037
1015
  let pathsToCheckPointer = { value: new Set<string>() };
1016
+ if (authorityStorage.DEBUG_NEVER_GC) return pathsToCheckPointer;
1038
1017
 
1039
1018
  let garbageCollectTime = MAX_CHANGE_AGE * 2;
1040
1019
  runInfinitePoll(garbageCollectTime, async () => {
@@ -21,6 +21,7 @@ import { debugNodeThread } from "../-c-identity/IdentityController";
21
21
  import { authorityLookup } from "../0-path-value-core/AuthorityLookup";
22
22
  import { decodeParentFilter, encodeParentFilter, registerGetSpecForChildPath } from "../0-path-value-core/hackedPackedPathParentFiltering";
23
23
  import { removeRange, rangesOverlap } from "../rangeMath";
24
+ import { evaluateValidStates } from "../config2";
24
25
 
25
26
  setImmediate(() => import("../4-querysub/Querysub"));
26
27
 
@@ -489,7 +490,7 @@ export class RemoteWatcher {
489
490
  let config: WatchConfig = {
490
491
  paths: Array.from(paths),
491
492
  parentPaths: Array.from(parentPaths),
492
- fullHistory: Querysub.SEND_FULL_HISTORY_ON_INITIAL_SYNC,
493
+ fullHistory: evaluateValidStates(),
493
494
  };
494
495
  if (isDebugLogEnabled()) {
495
496
  for (let path of paths) {
@@ -691,8 +692,8 @@ registerGetSpecForChildPath(path => {
691
692
  if (remoteWatchRange) return remoteWatchRange.authoritySpec;
692
693
 
693
694
  if (!remoteWatcher.hasAnyDirectPathWatches(path)) {
694
- // 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.
695
- 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 });
695
+ // NOTE: This might mean that our Path Watcher and remote watcher are out of sync, which is a big problem and will break things.
696
+ console.error(`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 });
696
697
  }
697
698
  }
698
699
  return authorityLookup.getOurSpec();
@@ -20,7 +20,7 @@ import { logErrors } from "../errors";
20
20
  import { getParentPathStr, getPathFromStr, hack_stripPackedPath } from "../path";
21
21
  import { measureBlock, measureFnc } from "socket-function/src/profiling/measure";
22
22
  import { pathValueCommitter } from "../0-path-value-core/PathValueController";
23
- import { PathValue, Value, getNextTime, Time, ReadLock, MAX_CHANGE_AGE, authorityStorage, getCreatorId, WatchConfig, hashPathForTransaction } from "../0-path-value-core/pathValueCore";
23
+ import { PathValue, Value, getNextTime, Time, ReadLock, MAX_CHANGE_AGE, authorityStorage, getCreatorId, WatchConfig, hashPathForTransaction, debugTime, compareTime } from "../0-path-value-core/pathValueCore";
24
24
  import { pathWatcher } from "../0-path-value-core/PathWatcher";
25
25
  import { blue, green, red } from "socket-function/src/formatting/logColors";
26
26
  import { runInfinitePoll } from "socket-function/src/batching";
@@ -32,6 +32,7 @@ import { remoteWatcher } from "./RemoteWatcher";
32
32
  import { heapTagObj } from "../diagnostics/heapTag";
33
33
  import { isDevDebugbreak } from "../config";
34
34
  import { matchesParentRangeFilter } from "../0-path-value-core/hackedPackedPathParentFiltering";
35
+ import { LOCAL_DOMAIN_PATH } from "../0-path-value-core/PathRouter";
35
36
 
36
37
  const pathValueCtor = heapTagObj("PathValue");
37
38
 
@@ -685,7 +686,25 @@ export class ClientWatcher {
685
686
  historyBasedReads?: boolean;
686
687
  }
687
688
  ): PathValue[] {
688
- const { values, locks, eventPaths } = config;
689
+ let { values, locks, eventPaths } = config;
690
+
691
+ locks = locks.filter(x => {
692
+ if (x.path.startsWith(LOCAL_DOMAIN_PATH)) {
693
+ console.error(`Tried to lock a local path. This would result in expected rejections, ignoring lock`, { path: x.path });
694
+ return false;
695
+ }
696
+ return true;
697
+ });
698
+
699
+ // Ensure all locks have the same end time.
700
+ if (locks.length > 0) {
701
+ let endTime = locks[0].endTime;
702
+ for (let lock of locks) {
703
+ if (compareTime(lock.endTime, endTime) !== 0) {
704
+ console.error(`Locks have inconsistent end times. ${debugTime(lock.endTime)} !== ${debugTime(endTime)}. Path: ${lock.path}`);
705
+ }
706
+ }
707
+ }
689
708
 
690
709
  // We trust our caller to use getFreeWriteTime, as they need to know the exact time
691
710
  // they are using so they can use it for their readTimes
@@ -737,6 +756,13 @@ export class ClientWatcher {
737
756
  });
738
757
  pathValues.push(pathValue);
739
758
  }
759
+ for (let pathValue of pathValues) {
760
+ // Ensure local values never have any locks. Local rejections are unexpected anyways, and they will not be rerun by the function runner, so they are generally worse than leaving an inconsistent value.
761
+ if (pathValue.locks.length > 0 && pathValue.path.startsWith(LOCAL_DOMAIN_PATH)) {
762
+ pathValue.locks = [];
763
+ pathValue.lockCount = 0;
764
+ }
765
+ }
740
766
 
741
767
  if (!config.dryRun) {
742
768
  for (let pv of pathValues) {
@@ -13,7 +13,7 @@ import { ActionsHistory } from "../diagnostics/ActionsHistory";
13
13
  import { registerResource } from "../diagnostics/trackResources";
14
14
  import { ClientWatcher, WatchSpecData, clientWatcher } from "../1-path-client/pathValueClientWatcher";
15
15
  import { createPathValueProxy, getPathFromProxy, getProxyPath, getProxyPathAndWatch, isValueProxy, isValueProxy2 } from "./pathValueProxy";
16
- import { authorityStorage, compareTime, ReadLock, epochTime, getNextTime, MAX_ACCEPTED_CHANGE_AGE, PathValue, Time, getCreatorId, getTimeUnique } from "../0-path-value-core/pathValueCore";
16
+ import { authorityStorage, compareTime, ReadLock, epochTime, getNextTime, MAX_ACCEPTED_CHANGE_AGE, PathValue, Time, getCreatorId, getTimeUnique, debugTime } from "../0-path-value-core/pathValueCore";
17
17
  import { runCodeWithDatabase, rawSchema } from "./pathDatabaseProxyBase";
18
18
  import debugbreak from "debugbreak";
19
19
  import { pathValueCommitter } from "../0-path-value-core/PathValueController";
@@ -589,7 +589,7 @@ export class PathValueProxyWatcher {
589
589
 
590
590
  const currentReadTime = watcher.options.forceReadLatest ? undefined : watcher.currentReadTime;
591
591
 
592
- let pathValue = authorityStorage.getValueAtTime(pathStr, currentReadTime);
592
+ let pathValue = authorityStorage.getValueAtOrBeforeTime(pathStr, currentReadTime);
593
593
  if (!watcher.options.noSyncing) {
594
594
  // NOTE: If we have any value, we are always synced (that's what synced means, as if we aren't syncing,
595
595
  // we delete any values, to prevent stale values from being used).
@@ -1289,6 +1289,14 @@ export class PathValueProxyWatcher {
1289
1289
  // The read time equals the write time, so our locks use the same time as our write time, and
1290
1290
  // therefore lock the actual state we used.
1291
1291
  watcher.currentReadTime = watcher.nextWriteTime;
1292
+ // We need to actually read well before our write, otherwise, we might read previous iterations of the same write. This shouldn't break anything, As I don't think the callers really care about when we read from, as long as it's similar to the write time.
1293
+ if (watcher.currentReadTime) {
1294
+ watcher.currentReadTime = {
1295
+ time: watcher.currentReadTime.time,
1296
+ version: watcher.currentReadTime.version - 1_000_000,
1297
+ creatorId: watcher.currentReadTime.creatorId,
1298
+ };
1299
+ }
1292
1300
 
1293
1301
  watcher.permissionsChecker = options.getPermissionsCheck?.();
1294
1302
 
@@ -1550,13 +1558,16 @@ export class PathValueProxyWatcher {
1550
1558
  let setValues: PathValue[] | undefined;
1551
1559
 
1552
1560
  let prefixes = options.overrideAllowLockDomainsPrefixes || getAllowedLockDomainsPrefixes();
1553
- // This consumes our write time, as once we write, we can't reuse writeTimes!
1554
- // (If we do, it breaks dependencies).
1561
+ let currentReadTime = watcher.currentReadTime;
1562
+ if (!currentReadTime) {
1563
+ currentReadTime = getNextTime();
1564
+ }
1565
+
1555
1566
  let writeTime = watcher.nextWriteTime;
1556
- watcher.nextWriteTime = undefined;
1557
1567
  if (!writeTime) {
1558
1568
  writeTime = getNextTime();
1559
1569
  }
1570
+ watcher.nextWriteTime = undefined;
1560
1571
 
1561
1572
  if (
1562
1573
  !watcher.options.hasSideEffects
@@ -1573,7 +1584,7 @@ export class PathValueProxyWatcher {
1573
1584
  for (let time of watcher.pendingAccesses.keys()) {
1574
1585
  if (time === undefined) continue;
1575
1586
  // If we're reading from a specific time, and it isn't just our write time, then we're reading in the past.
1576
- if (compareTime(time, writeTime) !== 0) {
1587
+ if (compareTime(time, currentReadTime) !== 0) {
1577
1588
  historyBasedReads = true;
1578
1589
  break;
1579
1590
  }
@@ -1582,13 +1593,13 @@ export class PathValueProxyWatcher {
1582
1593
  if (!watcher.options.noLocks && !watcher.options.unsafeNoLocks) {
1583
1594
  for (let [readTime, values] of watcher.pendingAccesses) {
1584
1595
  if (!readTime) {
1585
- readTime = writeTime;
1596
+ readTime = currentReadTime;
1586
1597
  }
1587
1598
  for (let valueObj of values.values()) {
1588
1599
  if (valueObj.noLocks) continue;
1589
1600
  let value = valueObj.pathValue;
1590
- // If any value has no locks, AND is local, it won't be rejected, so we don't need to lock it
1591
- if (value.lockCount === 0 && value.path.startsWith(LOCAL_DOMAIN_PATH)) {
1601
+ // NO LOCKS on local paths. Local rejections are unexpected anyways, and they will not be rerun by the function runner, so they are generally worse than leaving an inconsistent value.
1602
+ if (value.path.startsWith(LOCAL_DOMAIN_PATH)) {
1592
1603
  continue;
1593
1604
  }
1594
1605
  if (!prefixes.some(x => value.path.startsWith(x))) continue;
@@ -1608,7 +1619,7 @@ export class PathValueProxyWatcher {
1608
1619
  }
1609
1620
  for (let [readTime, paths] of watcher.pendingEpochAccesses) {
1610
1621
  if (!readTime) {
1611
- readTime = writeTime;
1622
+ readTime = currentReadTime;
1612
1623
  }
1613
1624
  for (let path of paths) {
1614
1625
  if (!prefixes.some(x => path.startsWith(x))) continue;
@@ -1629,23 +1640,17 @@ export class PathValueProxyWatcher {
1629
1640
  // run in the present, but intentionally want to write in the past? This usually only
1630
1641
  // happens if we intentionally run in the past (in which case writeTime will be set,
1631
1642
  // so this if statement wouldn't run anyways).
1632
- let fixedWriteTime = writeTime;
1633
1643
  for (let lock of locks) {
1634
- if (compareTime(lock.endTime, fixedWriteTime) > 0) {
1635
- fixedWriteTime = {
1644
+ if (compareTime(lock.endTime, writeTime) > 0) {
1645
+ // I'm curious how this could even happen?
1646
+ require("debugbreak")(2);
1647
+ debugger;
1648
+ console.error(`Write time is before the end time of a lock. This is not allowed. Shifting write time forward ${debugTime(writeTime)} < ${debugTime(lock.endTime)}`);
1649
+ writeTime = clientWatcher.getFreeWriteTime({
1636
1650
  time: addEpsilons(lock.endTime.time, 1),
1637
- version: 0,
1651
+ version: lock.endTime.version,
1638
1652
  creatorId: getCreatorId(),
1639
- };
1640
- }
1641
- }
1642
- if (fixedWriteTime !== writeTime) {
1643
- let prevWriteTime = writeTime;
1644
- writeTime = fixedWriteTime;
1645
- for (let lock of locks) {
1646
- if (lock.endTime === prevWriteTime) {
1647
- lock.endTime = writeTime;
1648
- }
1653
+ });
1649
1654
  }
1650
1655
  }
1651
1656
  }
@@ -1,23 +1,29 @@
1
1
  import { delay } from "socket-function/src/batching";
2
- import { PathValue, hashPathForTransaction, compareTime, Time } from "../0-path-value-core/pathValueCore";
2
+ import { PathValue, hashPathForTransaction, compareTime, Time, authorityStorage, epochTime, createMissingEpochValue, MISSING_TRANSACTION_PART_TIMEOUT, debugPathValue } from "../0-path-value-core/pathValueCore";
3
3
  import { SyncWatcher, proxyWatcher } from "./PathValueProxyWatcher";
4
+ import { isDefined } from "../misc";
4
5
 
5
- const MISSING_TRANSACTION_PART_TIMEOUT = 5000;
6
-
7
- export function isMissingTransactionPart(pathValues: PathValue[]): { time: number; path: string; } | undefined {
6
+ // NOTE: If we haven't synchronized a path, we'll know it's not synchronized. However, we might have synchronized the path, but we might have an older value. If we take one of the values we've read, we find all the transaction paths in it (which it will tell us directly), And there's other paths that we've also read at the same time in our path values. Except they have a time before the write of the value that we looked at the transaction paths, then it means we don't have the latest values for that transaction. And because we have the path value, we know we're watching it because we watch everything that we access. And so we know we're missing part of the transaction and should wait.
7
+ export function isMissingTransactionPart(pathValues: PathValue[], timeoutCheck?: boolean): { time: number; path: string; } | undefined {
8
8
  // Create a map of path hash -> time for quick lookups
9
9
  const pathHashToTime = new Map<string, {
10
10
  time: Time;
11
11
  path: string;
12
12
  }>();
13
- for (const pv of pathValues) {
14
- const hash = hashPathForTransaction(pv.path);
13
+ for (const pathValue of pathValues) {
14
+ if (!pathValue.valid) {
15
+ if (timeoutCheck) return undefined;
16
+ require("debugbreak")(2);
17
+ debugger;
18
+ throw new Error(`Cannot validate a transaction where part of it is invalid. We shouldn't have even been passed this value. Value: ${debugPathValue(pathValue)}`);
19
+ }
20
+ const hash = hashPathForTransaction(pathValue.path);
15
21
  const existing = pathHashToTime.get(hash);
16
22
  // Keep the latest time for this path
17
- if (!existing || compareTime(pv.time, existing.time) > 0) {
23
+ if (!existing || compareTime(pathValue.time, existing.time) > 0) {
18
24
  pathHashToTime.set(hash, {
19
- time: pv.time,
20
- path: pv.path,
25
+ time: pathValue.time,
26
+ path: pathValue.path,
21
27
  });
22
28
  }
23
29
  }
@@ -28,9 +34,7 @@ export function isMissingTransactionPart(pathValues: PathValue[]): { time: numbe
28
34
  if (!pathValue.transactionPaths || pathValue.transactionPaths.length === 0) {
29
35
  continue;
30
36
  }
31
-
32
- const existing = uniqueTransactions.get(pathValue.transactionPaths);
33
- if (!existing) {
37
+ if (!uniqueTransactions.has(pathValue.transactionPaths)) {
34
38
  uniqueTransactions.set(pathValue.transactionPaths, pathValue.time);
35
39
  }
36
40
  }
@@ -41,18 +45,18 @@ export function isMissingTransactionPart(pathValues: PathValue[]): { time: numbe
41
45
  } | undefined = undefined;
42
46
 
43
47
  // Check each unique transaction only once
44
- for (const [transactionPaths, transactionTime] of uniqueTransactions) {
45
- for (const transactionPathHash of transactionPaths) {
46
- const pathTime = pathHashToTime.get(transactionPathHash);
47
- if (!pathTime) continue;
48
-
49
- // We know that there's a value with this path hash at the transaction time. And so if the value that we've read is before that, then we haven't read the latest value. So we know that we're missing part of the transaction.
50
- const timeComparison = compareTime(pathTime.time, transactionTime);
51
- if (timeComparison < 0) {
48
+ for (let [transactionPaths, transactionTime] of uniqueTransactions) {
49
+ // It's normal if some of the paths aren't used. That's fine. You're not going to use the entire part of the transaction. Just because someone wrote a whole bunch of values at once doesn't mean you're gonna to read them all at once.
50
+ let values = transactionPaths.map(path => pathHashToTime.get(hashPathForTransaction(path))).filter(isDefined);
51
+ for (let value of values) {
52
+ // We KNOW there's a value for all the transactions paths at transactionTime. We might have a NEWER value as well, so we don't do !==, BUT, if we only have an older value, it means we need to wait to receive the full transaction.
53
+ if (compareTime(value.time, transactionTime) < 0) {
54
+ // Keep track of the newest transaction time, not the value time. The transaction time is the thing we're waiting for. We're waiting for it to get old enough that we should have received the value. And if we don't receive it by then, we can warn.
55
+ // - If it's an older value, then we would have received it, unless we weren't watching the path. And if we just watch the path, then our synchronization check will ensure that we receive all of the paths. So this is only needed for newer values on paths that we're already watching, in which case, the timer receive it should be very similar to the transaction time.
52
56
  if (!newestWaitTime || transactionTime.time > newestWaitTime.time) {
53
57
  newestWaitTime = {
54
58
  time: transactionTime.time,
55
- path: pathTime.path,
59
+ path: transactionPaths[0],
56
60
  };
57
61
  }
58
62
  }
@@ -69,6 +73,16 @@ export function getAllPendingPathValueReads(watcher = proxyWatcher.getTriggeredW
69
73
  values.push(pathValue.pathValue);
70
74
  }
71
75
  }
76
+ // NOTE: These can't contribute the transaction paths, but they do contribute their path and their time, which will cause other transaction paths to know that they are invalid.
77
+ let allEpochPaths = new Set<string>();
78
+ for (let paths of watcher.pendingEpochAccesses.values()) {
79
+ for (let path of paths) {
80
+ allEpochPaths.add(path);
81
+ }
82
+ }
83
+ for (let path of allEpochPaths) {
84
+ values.push(createMissingEpochValue(path));
85
+ }
72
86
  return values;
73
87
  }
74
88
 
@@ -77,7 +91,9 @@ export function waitIfReceivedIncompleteTransaction(watcher: SyncWatcher) {
77
91
  // We don't want to register a whole bunch of duplicate promises. So if somebody's waiting, there's no need to even do our check.
78
92
  if (watcher.specialPromiseUnsynced) return;
79
93
 
80
- let newestTime = isMissingTransactionPart(getAllPendingPathValueReads(watcher));
94
+ let readTime = watcher.currentReadTime;
95
+ let pendingValuePaths = getAllPendingPathValueReads(watcher);
96
+ const newestTime = isMissingTransactionPart(pendingValuePaths);
81
97
  if (!newestTime) return;
82
98
 
83
99
  let timeout = newestTime.time + MISSING_TRANSACTION_PART_TIMEOUT;
@@ -85,6 +101,12 @@ export function waitIfReceivedIncompleteTransaction(watcher: SyncWatcher) {
85
101
  if (timeout < now) return;
86
102
 
87
103
  let promise = delay(timeout - now);
104
+ void promise.finally(() => {
105
+ let latestValues = pendingValuePaths.map(x => authorityStorage.getValueAtOrBeforeTime(x.path, readTime) || createMissingEpochValue(x.path));
106
+ if (isMissingTransactionPart(latestValues, true)) {
107
+ console.error(`Transaction never became complete, at path ${newestTime.path} for time ${newestTime.time}`, { readTime, paths: pendingValuePaths.map(x => x.path) });
108
+ }
109
+ });
88
110
  // This really nicely both blocks it from finishing because of the waiting for the promise and also triggers it automatically when the delay finishes. HOWEVER, In practice, the promise should never be required to trigger it. It should trigger when we receive the missing parts of the transaction, which we will be, of course, watching as they're in the path values that we're accessing. The timeout is just in case something goes wrong and we incorrectly think we should receive the values, but we won't. That way, eventually, we do commit the value.
89
111
  // ALSO! Plus the hash I think only stores like 48 bits. So the chance of collision is somewhat high, especially if we're accessing thousands of paths. So sometimes this will trigger even though we're not missing any part just because we had a collision between the path hashes.
90
112
  proxyWatcher.triggerOnPromiseFinish(promise, { waitReason: "Missing transaction part" });