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.
- package/package.json +1 -1
- package/src/-d-trust/NetworkTrust2.ts +1 -1
- package/src/0-path-value-core/LockWatcher2.ts +2 -5
- package/src/0-path-value-core/PathRouter.ts +2 -3
- package/src/0-path-value-core/PathRouterConstants.ts +4 -0
- package/src/0-path-value-core/PathValueCommitter.ts +0 -1
- package/src/0-path-value-core/PathValueController.ts +1 -1
- package/src/0-path-value-core/PathWatcher.ts +14 -5
- package/src/0-path-value-core/ValidStateComputer.ts +234 -86
- package/src/0-path-value-core/pathValueCore.ts +57 -78
- package/src/1-path-client/RemoteWatcher.ts +4 -3
- package/src/1-path-client/pathValueClientWatcher.ts +28 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +29 -24
- package/src/2-proxy/TransactionDelayer.ts +44 -22
- package/src/2-proxy/archiveMoveHarness.ts +2 -2
- package/src/3-path-functions/PathFunctionRunner.ts +30 -22
- package/src/3-path-functions/PathFunctionRunnerMain.ts +1 -3
- package/src/4-deploy/deployFunctions.ts +1 -1
- package/src/4-deploy/deployMain.ts +1 -1
- package/src/4-deploy/edgeClientWatcher.tsx +1 -1
- package/src/4-deploy/edgeNodes.ts +1 -1
- package/src/4-dom/qreactTest.tsx +1 -1
- package/src/4-querysub/Querysub.ts +8 -9
- package/src/4-querysub/QuerysubController.ts +19 -1
- package/src/4-querysub/permissions.ts +1 -1
- package/src/4-querysub/predictionQueue.tsx +1 -1
- package/src/4-querysub/querysubPrediction.ts +25 -12
- package/src/5-diagnostics/GenericFormat.tsx +1 -1
- package/src/5-diagnostics/qreactDebug.tsx +2 -2
- package/src/archiveapps/archiveGCEntry.tsx +1 -1
- package/src/archiveapps/archiveJoinEntry.ts +3 -3
- package/src/config.ts +5 -1
- package/src/config2.ts +9 -7
- package/src/deployManager/components/CommitModal.tsx +1 -1
- package/src/deployManager/components/DeployProgressView.tsx +1 -1
- package/src/deployManager/components/MachineDetailPage.tsx +1 -1
- package/src/deployManager/components/MachinesListPage.tsx +1 -1
- package/src/deployManager/components/ServiceDetailPage.tsx +1 -1
- package/src/deployManager/components/ServicesListPage.tsx +1 -1
- package/src/deployManager/components/Tools.tsx +1 -1
- package/src/deployManager/machineApplyMainCode.ts +3 -3
- package/src/deployManager/machineController.ts +1 -1
- package/src/deployManager/machineSchema.ts +1 -1
- package/src/deployManager/setupMachineMain.ts +1 -1
- package/src/diagnostics/FunctionCallInfoState.ts +6 -3
- package/src/diagnostics/MachineThreadInfo.tsx +1 -1
- package/src/diagnostics/NodeViewer.tsx +2 -1
- package/src/diagnostics/StatWarning.tsx +56 -0
- package/src/diagnostics/StatsHeader.tsx +248 -0
- package/src/diagnostics/StatsOverrides.ts +50 -0
- package/src/diagnostics/SyncTestPage.tsx +3 -3
- package/src/diagnostics/TimeDebug.tsx +1 -1
- package/src/diagnostics/debugger/mcp-server.ts +1 -1
- package/src/diagnostics/grossStats/GrossStatsPage.tsx +1 -1
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +1 -1
- package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts +1 -1
- package/src/diagnostics/logs/TimeRangeSelector.tsx +1 -1
- package/src/diagnostics/logs/errorNotifications2/ErrorNotificationPage.tsx +1 -1
- package/src/diagnostics/logs/errorNotifications2/ErrorWarning.tsx +0 -1
- package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +1 -1
- package/src/diagnostics/logs/errorNotifications2/errorWatchEntry.ts +1 -1
- package/src/diagnostics/logs/errorNotifications2/errorWatcher.ts +1 -1
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +1 -1
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +1 -1
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +1 -1
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +1 -1
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +1 -1
- package/src/diagnostics/managementPages.tsx +4 -9
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +1 -1
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +1 -1
- package/src/diagnostics/misc-pages/DNSPage.tsx +1 -1
- package/src/diagnostics/pathAuditer.ts +6 -6
- package/src/diagnostics/statsDefinitions.tsx +253 -0
- package/src/functional/throttleRender.ts +1 -1
- package/src/library-components/AspectSizedComponent.tsx +1 -1
- package/src/library-components/Button.tsx +1 -1
- package/src/library-components/ButtonSelector.tsx +1 -1
- package/src/library-components/DropdownCustom.tsx +1 -1
- package/src/library-components/DropdownSelector.tsx +1 -1
- package/src/library-components/Histogram.tsx +1 -1
- package/src/library-components/InlinePopup.tsx +1 -1
- package/src/library-components/LazyComponent.tsx +5 -1
- package/src/library-components/StickyBottomScroll.tsx +1 -1
- package/src/library-components/TypedConfigEditor.tsx +1 -1
- package/src/library-components/URLParam.ts +5 -9
- package/src/library-components/drag.ts +1 -1
- package/src/misc/formatJSX.tsx +1 -1
- package/src/user-implementation/userData.ts +1 -1
- package/test2.ts +1 -1
- package/testEntry2.ts +1 -1
- package/valid.md +205 -0
- package/src/deployManager/LaunchTrackingHeader.tsx +0 -65
- package/src/diagnostics/FunctionCallInfo.tsx +0 -141
- package/src/diagnostics/PathDistributionInfo.tsx +0 -110
- 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
|
-
|
|
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.
|
|
461
|
+
return pathValueSerializer.getPathValue(this.getValueAtOrBeforeTime(path));
|
|
455
462
|
}
|
|
456
|
-
public
|
|
457
|
-
let value = this.
|
|
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
|
|
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.
|
|
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
|
-
//
|
|
784
|
-
|
|
785
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
902
|
-
|
|
903
|
-
//
|
|
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
|
|
906
|
-
//
|
|
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 -
|
|
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
|
|
910
|
+
let firstGoldenIndex = values.findIndex(x => x.valid && (x.time.time < goldenTime));
|
|
914
911
|
if (firstGoldenIndex < 0) return;
|
|
915
912
|
|
|
916
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
695
|
-
console.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1554
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
//
|
|
1591
|
-
if (value.
|
|
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 =
|
|
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,
|
|
1635
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
14
|
-
|
|
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(
|
|
23
|
+
if (!existing || compareTime(pathValue.time, existing.time) > 0) {
|
|
18
24
|
pathHashToTime.set(hash, {
|
|
19
|
-
time:
|
|
20
|
-
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 (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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:
|
|
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
|
|
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" });
|