querysub 0.403.0 → 0.405.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/.cursorrules +2 -0
- package/bin/audit-imports.js +4 -0
- package/bin/join.js +1 -1
- package/package.json +7 -4
- package/spec.txt +77 -0
- package/src/-a-archives/archiveCache.ts +9 -4
- package/src/-a-archives/archivesBackBlaze.ts +1039 -1039
- package/src/-a-auth/certs.ts +0 -12
- package/src/-c-identity/IdentityController.ts +12 -3
- package/src/-f-node-discovery/NodeDiscovery.ts +32 -26
- package/src/-g-core-values/NodeCapabilities.ts +12 -2
- package/src/0-path-value-core/AuthorityLookup.ts +239 -0
- package/src/0-path-value-core/LockWatcher2.ts +150 -0
- package/src/0-path-value-core/PathRouter.ts +543 -0
- package/src/0-path-value-core/PathRouterRouteOverride.ts +72 -0
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +73 -0
- package/src/0-path-value-core/PathValueCommitter.ts +222 -488
- package/src/0-path-value-core/PathValueController.ts +277 -239
- package/src/0-path-value-core/PathWatcher.ts +534 -0
- package/src/0-path-value-core/ShardPrefixes.ts +31 -0
- package/src/0-path-value-core/ValidStateComputer.ts +303 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +1 -1
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +80 -44
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +13 -16
- package/src/0-path-value-core/auditLogs.ts +2 -0
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +97 -0
- package/src/0-path-value-core/pathValueArchives.ts +491 -492
- package/src/0-path-value-core/pathValueCore.ts +195 -1496
- package/src/0-path-value-core/startupAuthority.ts +74 -0
- package/src/1-path-client/RemoteWatcher.ts +90 -82
- package/src/1-path-client/pathValueClientWatcher.ts +808 -815
- package/src/2-proxy/PathValueProxyWatcher.ts +10 -8
- package/src/2-proxy/archiveMoveHarness.ts +182 -214
- package/src/2-proxy/garbageCollection.ts +9 -8
- package/src/2-proxy/schema2.ts +21 -1
- package/src/3-path-functions/PathFunctionHelpers.ts +206 -180
- package/src/3-path-functions/PathFunctionRunner.ts +943 -766
- package/src/3-path-functions/PathFunctionRunnerMain.ts +5 -3
- package/src/3-path-functions/pathFunctionLoader.ts +2 -2
- package/src/3-path-functions/syncSchema.ts +596 -521
- package/src/4-deploy/deployFunctions.ts +19 -4
- package/src/4-deploy/deployGetFunctionsInner.ts +8 -2
- package/src/4-deploy/deployMain.ts +51 -68
- package/src/4-deploy/edgeClientWatcher.tsx +6 -1
- package/src/4-deploy/edgeNodes.ts +2 -2
- package/src/4-dom/qreact.tsx +2 -4
- package/src/4-dom/qreactTest.tsx +7 -13
- package/src/4-querysub/Querysub.ts +21 -8
- package/src/4-querysub/QuerysubController.ts +45 -29
- package/src/4-querysub/permissions.ts +2 -2
- package/src/4-querysub/querysubPrediction.ts +80 -70
- package/src/4-querysub/schemaHelpers.ts +5 -1
- package/src/5-diagnostics/GenericFormat.tsx +14 -9
- package/src/archiveapps/archiveGCEntry.tsx +9 -2
- package/src/archiveapps/archiveJoinEntry.ts +96 -84
- package/src/bits.ts +19 -0
- package/src/config.ts +21 -3
- package/src/config2.ts +23 -48
- package/src/deployManager/components/DeployPage.tsx +7 -3
- package/src/deployManager/machineSchema.ts +4 -1
- package/src/diagnostics/ActionsHistory.ts +3 -8
- package/src/diagnostics/AuditLogPage.tsx +2 -3
- package/src/diagnostics/FunctionCallInfo.tsx +141 -0
- package/src/diagnostics/FunctionCallInfoState.ts +162 -0
- package/src/diagnostics/MachineThreadInfo.tsx +1 -1
- package/src/diagnostics/NodeViewer.tsx +37 -48
- package/src/diagnostics/SyncTestPage.tsx +241 -0
- package/src/diagnostics/auditImportViolations.ts +185 -0
- package/src/diagnostics/listenOnDebugger.ts +3 -3
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +10 -4
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +2 -2
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +24 -22
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +1 -1
- package/src/diagnostics/logs/diskLogGlobalContext.ts +1 -0
- package/src/diagnostics/logs/errorNotifications2/logWatcher.ts +1 -3
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +34 -16
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +4 -6
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleInstanceTableView.tsx +36 -5
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +19 -5
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +15 -7
- package/src/diagnostics/logs/lifeCycleAnalysis/NestedLifeCycleInfo.tsx +28 -106
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMatching.ts +2 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMisc.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +18 -7
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +3 -0
- package/src/diagnostics/managementPages.tsx +10 -3
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +20 -26
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +6 -4
- package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +2 -2
- package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +7 -9
- package/src/diagnostics/misc-pages/SnapshotViewer.tsx +23 -12
- package/src/diagnostics/misc-pages/archiveViewerShared.tsx +1 -1
- package/src/diagnostics/pathAuditer.ts +486 -0
- package/src/diagnostics/pathAuditerCallback.ts +20 -0
- package/src/diagnostics/watchdog.ts +8 -1
- package/src/library-components/URLParam.ts +1 -1
- package/src/misc/hash.ts +1 -0
- package/src/path.ts +21 -7
- package/src/server.ts +54 -47
- package/src/user-implementation/loginEmail.tsx +1 -1
- package/tempnotes.txt +65 -0
- package/test.ts +298 -97
- package/src/0-path-value-core/NodePathAuthorities.ts +0 -1057
- package/src/0-path-value-core/PathController.ts +0 -1
- package/src/5-diagnostics/diskValueAudit.ts +0 -218
- package/src/5-diagnostics/memoryValueAudit.ts +0 -438
- package/src/archiveapps/archiveMergeEntry.tsx +0 -48
- package/src/archiveapps/lockTest.ts +0 -127
package/src/-a-auth/certs.ts
CHANGED
|
@@ -88,18 +88,6 @@ export function createX509(
|
|
|
88
88
|
|
|
89
89
|
let localHostDomain = "127-0-0-1." + domain.split(".").slice(-2).join(".");
|
|
90
90
|
|
|
91
|
-
//todonext
|
|
92
|
-
// Wait, why is one of our alt names not the machine URL that includes the machine hash? Oh, I guess it can't be because we don't know the hash until we create the cert, but we kind of do. Or we can at least.
|
|
93
|
-
// I guess the real question is why was this not an issue before? Were we adding it before and then we stopped adding it? Were we adding the thread certificate before to trust?
|
|
94
|
-
// I think it's fine to change this behavior, although it is kind of annoying to get the public key here (I think we have to derive it from the private key. What a nightmare.
|
|
95
|
-
// Maybe we had just an issue where we weren't correctly verifying the certificate? No, but that doesn't make any sense because Node.js is verifying the certificate.
|
|
96
|
-
//todonext
|
|
97
|
-
// Our trust store hasn't changed, it's just our machine ID that's changed. And even if it has changed, that's fine. But anyway, we should check our trust store to see what the old certificate looked like.
|
|
98
|
-
//todonext;
|
|
99
|
-
// Huh, the certificates did used to have the machine ID in them. And actually the domain I'm looking at, I'm pretty sure we were adding, that doesn't make any sense, the thread IDs? I don't know, maybe it was just totally borked before, maybe explain some of the issues we were having. It could be why sometimes startup failed. Maybe what happened was it only works if... When a node starts, it tells all other nodes that it changed the trust cache. And in that way, if they start in order, even though they keep breaking the trust cache by clobbering the machine CA certificate, We end up with all the certificates.
|
|
100
|
-
// I do really like the Node.js error. It's extremely specific. It even gives us the full cert alt names. Very good error. Without this error it would be extremely difficult to debug this.
|
|
101
|
-
|
|
102
|
-
|
|
103
91
|
extensions.push(...[
|
|
104
92
|
{ name: "keyUsage", keyCertSign: isCA, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true },
|
|
105
93
|
{ name: "subjectKeyIdentifier" },
|
|
@@ -9,7 +9,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
|
|
|
9
9
|
import { CallerContext } from "socket-function/SocketFunctionTypes";
|
|
10
10
|
import { cache, cacheWeak, lazy } from "socket-function/src/caching";
|
|
11
11
|
import { getClientNodeId, getNodeId, getNodeIdDomain, getNodeIdIP, getNodeIdLocation, isClientNodeId } from "socket-function/src/nodeCache";
|
|
12
|
-
import { decodeNodeId, getCommonName, getIdentityCA, getMachineId, getPublicIdentifier, getThreadKeyCert, parseCert, sign, validateCertificate, verify } from "../-a-auth/certs";
|
|
12
|
+
import { decodeNodeId, getCommonName, getIdentityCA, getMachineId, getOwnMachineId, getPublicIdentifier, getThreadKeyCert, parseCert, sign, validateCertificate, verify } from "../-a-auth/certs";
|
|
13
13
|
import { getShortNumber } from "../bits";
|
|
14
14
|
import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
|
|
15
15
|
import { timeoutToError } from "../errors";
|
|
@@ -18,7 +18,7 @@ import { formatTime } from "socket-function/src/formatting/format";
|
|
|
18
18
|
import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
|
|
19
19
|
import { red } from "socket-function/src/formatting/logColors";
|
|
20
20
|
import { isNode } from "typesafecss";
|
|
21
|
-
import { getOwnThreadId } from "../-f-node-discovery/NodeDiscovery";
|
|
21
|
+
import { areNodeIdsEqual, getOwnThreadId } from "../-f-node-discovery/NodeDiscovery";
|
|
22
22
|
|
|
23
23
|
let callerInfo = new Map<CallerContext, {
|
|
24
24
|
reconnectNodeId: string | undefined;
|
|
@@ -137,9 +137,18 @@ class IdentityControllerBase {
|
|
|
137
137
|
// (We don't have to worry about other servers on the same domain, as all servers
|
|
138
138
|
// on the same domain should be the same!)
|
|
139
139
|
let localNodeId = caller.localNodeId;
|
|
140
|
-
if (payload.serverId
|
|
140
|
+
if (!areNodeIdsEqual(payload.serverId, localNodeId)) {
|
|
141
141
|
throw new Error(`Identity is for another server! The connection is calling us ${localNodeId}, but signature is for ${payload.serverId}`);
|
|
142
142
|
}
|
|
143
|
+
let calledMachineId = getMachineId(payload.serverId);
|
|
144
|
+
if (calledMachineId !== "127-0-0-1" && calledMachineId !== getOwnMachineId()) {
|
|
145
|
+
throw new Error(`Tried to call a different machine. We are ${getOwnMachineId()}, they called ${calledMachineId}`);
|
|
146
|
+
}
|
|
147
|
+
let calledThreadId = decodeNodeId(payload.serverId)?.threadId;
|
|
148
|
+
if (calledThreadId && calledThreadId !== "127-0-0-1" && calledThreadId !== getOwnThreadId()) {
|
|
149
|
+
throw new Error(`Tried to call a different thread. We are ${getOwnThreadId()}, they called ${calledThreadId}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
143
152
|
|
|
144
153
|
// Verify the caller can sign as the cert
|
|
145
154
|
verify(payload.cert, signature, payload);
|
|
@@ -6,7 +6,7 @@ import { isNode, sha256Hash, throttleFunction, timeInMinute, timeInSecond } from
|
|
|
6
6
|
import { errorToUndefinedSilent, ignoreErrors, logErrors, timeoutToUndefinedSilent } from "../errors";
|
|
7
7
|
import { ensureWeAreTrusted, requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
8
8
|
import { delay, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
9
|
-
import { getNodeId, getNodeIdFromLocation } from "socket-function/src/nodeCache";
|
|
9
|
+
import { getNodeId, getNodeIdFromLocation, getNodeIdLocation } from "socket-function/src/nodeCache";
|
|
10
10
|
import { lazy } from "socket-function/src/caching";
|
|
11
11
|
import { shuffle } from "../misc/random";
|
|
12
12
|
import { blue, green, magenta, red, yellow } from "socket-function/src/formatting/logColors";
|
|
@@ -21,6 +21,7 @@ import { getBootedEdgeNode } from "../-0-hooks/hooks";
|
|
|
21
21
|
import { EdgeNodeConfig } from "../4-deploy/edgeNodes";
|
|
22
22
|
import * as certs from "../-a-auth/certs";
|
|
23
23
|
import { logDisk } from "../diagnostics/logs/diskLogger";
|
|
24
|
+
import { MaybePromise } from "socket-function/src/types";
|
|
24
25
|
|
|
25
26
|
let HEARTBEAT_INTERVAL = timeInMinute * 15;
|
|
26
27
|
// Interval which we check other heartbeats
|
|
@@ -64,6 +65,21 @@ export function isNodeDiscoveryLogging() {
|
|
|
64
65
|
return logging;
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
|
|
69
|
+
function getAlternateNodeIds(nodeId: string): MaybePromise<string[] | undefined> {
|
|
70
|
+
let machineId = certs.getMachineId(nodeId);
|
|
71
|
+
if (machineId === getOwnMachineId()) {
|
|
72
|
+
let decoded = decodeNodeId(nodeId);
|
|
73
|
+
if (decoded) {
|
|
74
|
+
return [
|
|
75
|
+
"127-0-0-1." + decoded.domain + ":" + decoded.port
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
SocketFunction.GET_ALTERNATE_NODE_IDS = getAlternateNodeIds;
|
|
82
|
+
|
|
67
83
|
export const getOurNodeId = getOwnNodeId;
|
|
68
84
|
export const getOurNodeIdAssert = getOwnNodeIdAssert;
|
|
69
85
|
|
|
@@ -110,14 +126,12 @@ export function isOwnNodeId(nodeId: string): boolean {
|
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
export function isNodeIdOnOwnMachineId(nodeId: string): boolean {
|
|
113
|
-
return certs.getMachineId(nodeId) === getOwnMachineId() || nodeId
|
|
129
|
+
return certs.getMachineId(nodeId) === getOwnMachineId() || decodeNodeId(nodeId)?.machineId === "127-0-0-1";
|
|
114
130
|
}
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
export function isNodeIdLocal(nodeId: string): boolean {
|
|
120
|
-
return nodeId.startsWith("127-0-0-1.");
|
|
132
|
+
export function areNodeIdsEqual(lhs: string, rhs: string): boolean {
|
|
133
|
+
if (lhs === rhs) return true;
|
|
134
|
+
return isNodeIdOnOwnMachineId(lhs) && isNodeIdOnOwnMachineId(rhs) && getNodeIdLocation(rhs)?.port === getNodeIdLocation(lhs)?.port;
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
let nodeOverrides: string[] | undefined;
|
|
@@ -173,13 +187,6 @@ function getAllNodesHash() {
|
|
|
173
187
|
}
|
|
174
188
|
function addNodeId(nodeId: string) {
|
|
175
189
|
addNodeIdBase(nodeId);
|
|
176
|
-
if (isNode() && isDevDebugbreak()) {
|
|
177
|
-
let obj = decodeNodeId(nodeId);
|
|
178
|
-
if (obj) {
|
|
179
|
-
let localNodeId = getNodeId("127-0-0-1." + getDomain(), obj.port);
|
|
180
|
-
addNodeIdBase(localNodeId);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
190
|
}
|
|
184
191
|
function addNodeIdBase(nodeId: string) {
|
|
185
192
|
if (allNodeIds2.has(nodeId)) return;
|
|
@@ -193,15 +200,6 @@ function setNodeIds(nodeIds: string[]) {
|
|
|
193
200
|
nodeIds = nodeIds.filter(x => x !== SPECIAL_NODE_ID_FOR_UNMOUNTED_NODE);
|
|
194
201
|
|
|
195
202
|
console.info("setNodeIds", { nodeIds });
|
|
196
|
-
// Also try all localhost ports, if we are developing and not in public mode
|
|
197
|
-
if (isNode() && !isPublic() && isDevDebugbreak()) {
|
|
198
|
-
let ports = new Set(nodeIds.map(nodeId => decodeNodeId(nodeId)?.port).filter(isDefined));
|
|
199
|
-
for (let port of ports) {
|
|
200
|
-
let localNodeId = getNodeId("127-0-0-1." + getDomain(), port);
|
|
201
|
-
nodeIds.push(localNodeId);
|
|
202
|
-
}
|
|
203
|
-
nodeIds = Array.from(new Set(nodeIds));
|
|
204
|
-
}
|
|
205
203
|
let newNodeIds = nodeIds.filter(nodeId => !allNodeIds2.has(nodeId));
|
|
206
204
|
let newIds = new Set(nodeIds);
|
|
207
205
|
let removedNodeIds = Array.from(allNodeIds2).filter(nodeId => !newIds.has(nodeId));
|
|
@@ -423,7 +421,7 @@ async function writeHeartbeat() {
|
|
|
423
421
|
await archives().set(nodeId, Buffer.from(now + ""));
|
|
424
422
|
}
|
|
425
423
|
|
|
426
|
-
async function runMainSyncLoops(
|
|
424
|
+
async function runMainSyncLoops() {
|
|
427
425
|
await syncArchives();
|
|
428
426
|
|
|
429
427
|
discoveryReady.resolve();
|
|
@@ -446,6 +444,8 @@ async function runMainSyncLoops(discoveryReady: PromiseObj<void>) {
|
|
|
446
444
|
await timeoutToUndefinedSilent(timeInSecond * 5, errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].addNode(getOwnNodeId())));
|
|
447
445
|
}));
|
|
448
446
|
|
|
447
|
+
nodeBroadcasted.resolve();
|
|
448
|
+
|
|
449
449
|
console.log(magenta(`Node discovery is loaded`));
|
|
450
450
|
|
|
451
451
|
await runInfinitePollCallAtStart(HEARTBEAT_INTERVAL, async function nodeDiscoverHeartbeat() {
|
|
@@ -477,6 +477,7 @@ async function runMainSyncLoops(discoveryReady: PromiseObj<void>) {
|
|
|
477
477
|
}
|
|
478
478
|
|
|
479
479
|
let discoveryReady = new PromiseObj<void>();
|
|
480
|
+
let nodeBroadcasted = new PromiseObj<void>();
|
|
480
481
|
beforeGetNodeAllId = async () => {
|
|
481
482
|
await discoveryReady.promise;
|
|
482
483
|
};
|
|
@@ -484,6 +485,9 @@ export async function onNodeDiscoveryReady() {
|
|
|
484
485
|
await getAllNodeIds();
|
|
485
486
|
}
|
|
486
487
|
|
|
488
|
+
export async function onNodeBroadcasted() {
|
|
489
|
+
await nodeBroadcasted.promise;
|
|
490
|
+
}
|
|
487
491
|
if (isServer()) {
|
|
488
492
|
setImmediate(async () => {
|
|
489
493
|
|
|
@@ -491,7 +495,7 @@ if (isServer()) {
|
|
|
491
495
|
logErrors(runMemoryAuditLoop());
|
|
492
496
|
// NOTE: We used to wait until we mounted, but... we should be able to find nodes
|
|
493
497
|
// before we mount, right? (And what if we never mount?)
|
|
494
|
-
runMainSyncLoops(
|
|
498
|
+
runMainSyncLoops().catch(e => {
|
|
495
499
|
discoveryReady.reject(e);
|
|
496
500
|
logErrors(Promise.reject(e));
|
|
497
501
|
});
|
|
@@ -500,6 +504,7 @@ if (isServer()) {
|
|
|
500
504
|
|
|
501
505
|
if (isNode()) {
|
|
502
506
|
discoveryReady.resolve();
|
|
507
|
+
nodeBroadcasted.resolve();
|
|
503
508
|
// Just get the archives, syncing again if we haven't synced in a while
|
|
504
509
|
let lastGetTime = 0;
|
|
505
510
|
beforeGetNodeAllId = async () => {
|
|
@@ -518,6 +523,7 @@ if (isServer()) {
|
|
|
518
523
|
let nodes = [edgeNode.host];
|
|
519
524
|
allNodeIds2 = new Set(nodes);
|
|
520
525
|
discoveryReady.resolve();
|
|
526
|
+
nodeBroadcasted.resolve();
|
|
521
527
|
|
|
522
528
|
// NOTE: We run into TLS issues (as in, our servers use self signed certs), if we try to talk to just
|
|
523
529
|
// any node, so... we better just talk to the edge node
|
|
@@ -570,7 +576,7 @@ class NodeDiscoveryControllerBase {
|
|
|
570
576
|
}
|
|
571
577
|
|
|
572
578
|
public async getAllNodeIds(): Promise<string[]> {
|
|
573
|
-
return Array.from(allNodeIds2)
|
|
579
|
+
return Array.from(allNodeIds2);
|
|
574
580
|
}
|
|
575
581
|
public async getNodeId() {
|
|
576
582
|
return SocketFunction.mountedNodeId;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
6
6
|
import { SocketRegistered } from "socket-function/SocketFunctionTypes";
|
|
7
7
|
import { errorToUndefined, errorToUndefinedSilent, timeoutToUndefinedSilent } from "../errors";
|
|
8
|
-
import { getAllNodeIds } from "../-f-node-discovery/NodeDiscovery";
|
|
8
|
+
import { getAllNodeIds, isNodeIdOnOwnMachineId } from "../-f-node-discovery/NodeDiscovery";
|
|
9
9
|
import { green, red, yellow } from "socket-function/src/formatting/logColors";
|
|
10
10
|
import { shuffle } from "../misc/random";
|
|
11
11
|
import { delay } from "socket-function/src/batching";
|
|
@@ -20,6 +20,7 @@ import { getOwnMachineId, decodeNodeId, decodeNodeIdAssert, getMachineId } from
|
|
|
20
20
|
import { sort } from "socket-function/src/misc";
|
|
21
21
|
import { getPathStr2 } from "../path";
|
|
22
22
|
import { PromiseObj } from "../promise";
|
|
23
|
+
import { getTrueTimeOffset } from "socket-function/time/trueTimeShim";
|
|
23
24
|
setImmediate(() => {
|
|
24
25
|
import("../diagnostics/MachineThreadInfo");
|
|
25
26
|
});
|
|
@@ -103,7 +104,7 @@ export async function getControllerNodeIdList(
|
|
|
103
104
|
|
|
104
105
|
let results = Array.from(passedNodeIds.entries());
|
|
105
106
|
// Prefer localhost connections as they're faster.
|
|
106
|
-
sort(results, (x) => x[0]
|
|
107
|
+
sort(results, (x) => isNodeIdOnOwnMachineId(x[0]) ? 0 : 1);
|
|
107
108
|
let lookup = new Map<string, { nodeId: string; entryPoint: string }>();
|
|
108
109
|
for (let x of results) {
|
|
109
110
|
let key = getPathStr2(x[1].machineId, decodeNodeIdAssert(x[0]).port.toString());
|
|
@@ -142,6 +143,10 @@ class NodeCapabilitiesControllerBase {
|
|
|
142
143
|
return getFunctionRunnerShards();
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
public async getTrueTimeOffset() {
|
|
147
|
+
return getTrueTimeOffset();
|
|
148
|
+
}
|
|
149
|
+
|
|
145
150
|
public async getInspectURL() {
|
|
146
151
|
return await getDebuggerUrl();
|
|
147
152
|
}
|
|
@@ -185,7 +190,12 @@ export const NodeCapabilitiesController = SocketFunction.register(
|
|
|
185
190
|
getStartupTime: {},
|
|
186
191
|
getMemoryUsage: {},
|
|
187
192
|
getFunctionRunnerShards: {},
|
|
193
|
+
getTrueTimeOffset: {},
|
|
188
194
|
getInspectURL: { hooks: [requiresNetworkTrustHook] },
|
|
189
195
|
exposeExternalDebugPortOnce: { hooks: [requiresNetworkTrustHook] },
|
|
196
|
+
}),
|
|
197
|
+
() => ({
|
|
198
|
+
// I think assert is management user didn't exist when we wrote these functions, and so we just made them public. But now that it does exist, we might as well use it. Servers will count as management users anyway, and I don't see any reason a non-management user or a non-server needs to access any of these functions.
|
|
199
|
+
hooks: [(require("../diagnostics/managementPages") as typeof import("../diagnostics/managementPages")).assertIsManagementUser],
|
|
190
200
|
})
|
|
191
201
|
);
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
2
|
+
import { nestArchives } from "../-a-archives/archives";
|
|
3
|
+
import { getArchivesBackblaze } from "../-a-archives/archivesBackBlaze";
|
|
4
|
+
import { archiveJSONT } from "../-a-archives/archivesJSONT";
|
|
5
|
+
import { getDomain, isPublic } from "../config";
|
|
6
|
+
import { cache, lazy } from "socket-function/src/caching";
|
|
7
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
8
|
+
import { runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
9
|
+
import { getAllNodeIds, getOwnNodeId, isOwnNodeId, onNodeBroadcasted, syncNodesNow, watchDeltaNodeIds, watchNodeIds } from "../-f-node-discovery/NodeDiscovery";
|
|
10
|
+
import { IdentityController_getCurrentReconnectNodeIdAssert } from "../-c-identity/IdentityController";
|
|
11
|
+
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
12
|
+
import { isClient } from "../config2";
|
|
13
|
+
import { getPathStr1 } from "../path";
|
|
14
|
+
import { timeoutToError } from "../errors";
|
|
15
|
+
import { AuthoritySpec } from "./PathRouter";
|
|
16
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
17
|
+
import { getAllAuthoritySpec, getEmptyAuthoritySpec } from "./PathRouterServerAuthoritySpec";
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
let NETWORK_POLL_INTERVAL = timeInMinute * 5;
|
|
21
|
+
let CALL_TIMEOUT = isPublic() ? timeInSecond * 20 : timeInSecond * 3;
|
|
22
|
+
|
|
23
|
+
export type AuthorityEntry = {
|
|
24
|
+
nodeId: string;
|
|
25
|
+
authoritySpec: AuthoritySpec;
|
|
26
|
+
entryReceivedTime: number;
|
|
27
|
+
isReady: boolean;
|
|
28
|
+
};
|
|
29
|
+
type AuthorityTopology = {
|
|
30
|
+
// nodeId =>
|
|
31
|
+
nodes: Map<string, AuthorityEntry>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
class AuthorityLookup {
|
|
35
|
+
private topology: AuthorityTopology = {
|
|
36
|
+
nodes: new Map(),
|
|
37
|
+
};
|
|
38
|
+
private ourSpec: AuthoritySpec = getEmptyAuthoritySpec();
|
|
39
|
+
private ourIsReady = false;
|
|
40
|
+
private didInitialSync = false;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
public async getTopology() {
|
|
44
|
+
await this.startSyncing();
|
|
45
|
+
return Array.from(this.topology.nodes.values());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public getTopologySync() {
|
|
49
|
+
if (!this.didInitialSync) throw new Error("Cannot call getTopologySync without calling syncAllNow at some point first.");
|
|
50
|
+
return Array.from(this.topology.nodes.values()).filter(x => x.isReady);
|
|
51
|
+
}
|
|
52
|
+
public getAuthoritySpecForNodeId(nodeId: string): AuthoritySpec | undefined {
|
|
53
|
+
if (!this.didInitialSync) throw new Error("Cannot call getAuthoritySpecForNodeId without calling syncAllNow at some point first.");
|
|
54
|
+
return this.topology.nodes.get(nodeId)?.authoritySpec;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public getOurSpec() {
|
|
58
|
+
this.ourSpec.nodeId = getOwnNodeId();
|
|
59
|
+
return this.ourSpec;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private setSpec = false;
|
|
63
|
+
public async setOurSpec(spec: AuthoritySpec) {
|
|
64
|
+
if (!SocketFunction.isMounted()) throw new Error("Cannot call setOurPaths without mounting first (use Querysub.hostService).");
|
|
65
|
+
spec.nodeId = getOwnNodeId();
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if (this.setSpec) {
|
|
69
|
+
throw new Error("You cannot redefine what authority paths you have. If you remove any authority paths, then you will no longer receive new value rights, but your watchers won't know, and so they will become broken. IF You are only adding paths, this might allowed, and we need to update this code to check for that (you will still need to make sure you synchronize all the values first, etc).");
|
|
70
|
+
}
|
|
71
|
+
this.setSpec = true;
|
|
72
|
+
|
|
73
|
+
this.ourSpec = spec;
|
|
74
|
+
this.updatePaths(getOwnNodeId(), spec, false);
|
|
75
|
+
|
|
76
|
+
await this.syncAllNow();
|
|
77
|
+
}
|
|
78
|
+
public async setIsReady() {
|
|
79
|
+
if (!this.setSpec) throw new Error("Cannot call setIsReady without calling setOurPaths first.");
|
|
80
|
+
this.ourIsReady = true;
|
|
81
|
+
this.updatePaths(getOwnNodeId(), this.ourSpec, true);
|
|
82
|
+
await this.syncAllNow();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async syncAllNow() {
|
|
86
|
+
if (!this.didInitialSync) {
|
|
87
|
+
await this.startSyncing();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let time = Date.now();
|
|
91
|
+
console.log(`Syncing all nodes now`);
|
|
92
|
+
// Sync again. There are some important points when there could be races, and so syncing everything now should help eliminate that.
|
|
93
|
+
await syncNodesNow();
|
|
94
|
+
let allNodeIds = await getAllNodeIds();
|
|
95
|
+
await Promise.all(allNodeIds.map(async nodeId => {
|
|
96
|
+
if (isOwnNodeId(nodeId)) return;
|
|
97
|
+
await this.syncNodeSerial(nodeId)();
|
|
98
|
+
}));
|
|
99
|
+
console.log(`Finished syncing all nodes now, took ${formatTime(Date.now() - time)}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
public startSyncing = lazy(async () => {
|
|
104
|
+
await onNodeBroadcasted();
|
|
105
|
+
let firstSync = true;
|
|
106
|
+
let promises: Promise<void>[] = [];
|
|
107
|
+
watchDeltaNodeIds(({ newNodeIds, removedNodeIds }) => {
|
|
108
|
+
for (let nodeId of newNodeIds) {
|
|
109
|
+
try {
|
|
110
|
+
let { promise } = this.connectTo(nodeId);
|
|
111
|
+
if (firstSync) {
|
|
112
|
+
promises.push(promise);
|
|
113
|
+
}
|
|
114
|
+
} catch { }
|
|
115
|
+
}
|
|
116
|
+
firstSync = false;
|
|
117
|
+
for (let nodeId of removedNodeIds) {
|
|
118
|
+
this.connectTo(nodeId).onNodeRemoved();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
await Promise.all(promises);
|
|
122
|
+
this.didInitialSync = true;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
private updatePaths(nodeId: string, spec: AuthoritySpec, isReady: boolean) {
|
|
127
|
+
let entry: AuthorityEntry = {
|
|
128
|
+
nodeId,
|
|
129
|
+
authoritySpec: spec,
|
|
130
|
+
entryReceivedTime: Date.now(),
|
|
131
|
+
isReady,
|
|
132
|
+
};
|
|
133
|
+
let prevEntry = this.topology.nodes.get(nodeId);
|
|
134
|
+
// Try to preserve === property where possible, so we can use it as a cache key
|
|
135
|
+
if (prevEntry && JSON.stringify(prevEntry.authoritySpec) === JSON.stringify(spec)) {
|
|
136
|
+
entry.authoritySpec = prevEntry.authoritySpec;
|
|
137
|
+
}
|
|
138
|
+
this.topology.nodes.set(nodeId, entry);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public async networkSyncPaths(otherSpec: AuthoritySpec, isReady: boolean): Promise<{
|
|
142
|
+
spec: AuthoritySpec;
|
|
143
|
+
isReady: boolean;
|
|
144
|
+
}> {
|
|
145
|
+
let nodeId = IdentityController_getCurrentReconnectNodeIdAssert();
|
|
146
|
+
this.updatePaths(nodeId, otherSpec, isReady);
|
|
147
|
+
console.info(`Received network sync, returning our data`, {
|
|
148
|
+
nodeId,
|
|
149
|
+
otherSpec,
|
|
150
|
+
isReady,
|
|
151
|
+
ourSpec: this.ourSpec,
|
|
152
|
+
ourIsReady: this.ourIsReady,
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
spec: this.ourSpec,
|
|
156
|
+
isReady: this.ourIsReady,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
private connectTo = cache((nodeId: string) => {
|
|
164
|
+
let stopObj = { stop: false };
|
|
165
|
+
let promise = runInfinitePollCallAtStart(NETWORK_POLL_INTERVAL, async () => {
|
|
166
|
+
await this.syncNodeSerial(nodeId)();
|
|
167
|
+
}, stopObj);
|
|
168
|
+
|
|
169
|
+
const onNodeRemoved = () => {
|
|
170
|
+
this.topology.nodes.delete(nodeId);
|
|
171
|
+
stopObj.stop = true;
|
|
172
|
+
this.connectTo.clear(nodeId);
|
|
173
|
+
this.disconnectWatch.clear(nodeId);
|
|
174
|
+
};
|
|
175
|
+
return { onNodeRemoved, promise };
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
private syncNodeSerial = cache((nodeId: string) => {
|
|
180
|
+
return runInSerial(async () => {
|
|
181
|
+
await this.syncNodeNow(nodeId);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
private disconnectWatch = cache((nodeId: string) => {
|
|
185
|
+
let onDisconnect = () => {
|
|
186
|
+
SocketFunction.onNextDisconnect(nodeId, onDisconnect);
|
|
187
|
+
this.topology.nodes.delete(nodeId);
|
|
188
|
+
};
|
|
189
|
+
SocketFunction.onNextDisconnect(nodeId, onDisconnect);
|
|
190
|
+
});
|
|
191
|
+
private async syncNodeNow(nodeId: string) {
|
|
192
|
+
try {
|
|
193
|
+
await this.syncNodeNowBase(nodeId);
|
|
194
|
+
} catch {
|
|
195
|
+
this.topology.nodes.delete(nodeId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
private async syncNodeNowBase(nodeId: string) {
|
|
201
|
+
if (isClient()) {
|
|
202
|
+
// Doesn't matter what the node ID is, we should really only be connecting to the browser node ID, Which will have all the data for the current domain.
|
|
203
|
+
// - Get all node IDs should restrict our nodes to just the browser node ID. If we ever change this, then either it's redundant nodes and they all have all the same data, or we need to figure out what data they have, And as their proxies, it probably won't be their actual authority data. So that will require new API functions, etc.
|
|
204
|
+
this.updatePaths(nodeId, {
|
|
205
|
+
...getAllAuthoritySpec(),
|
|
206
|
+
nodeId: nodeId,
|
|
207
|
+
}, true);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let otherPaths = await timeoutToError(
|
|
212
|
+
CALL_TIMEOUT,
|
|
213
|
+
AuthorityLookupController.nodes[nodeId].networkSyncPaths(this.ourSpec, this.ourIsReady),
|
|
214
|
+
() => new Error(`Timeout calling networkSyncPaths for ${nodeId}`)
|
|
215
|
+
);
|
|
216
|
+
console.info(`Finished network sync`, {
|
|
217
|
+
nodeId,
|
|
218
|
+
otherSpec: otherPaths.spec,
|
|
219
|
+
isReady: otherPaths.isReady,
|
|
220
|
+
ourSpec: this.ourSpec,
|
|
221
|
+
ourIsReady: this.ourIsReady,
|
|
222
|
+
});
|
|
223
|
+
this.updatePaths(nodeId, otherPaths.spec, otherPaths.isReady);
|
|
224
|
+
this.disconnectWatch(nodeId);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export const authorityLookup = new AuthorityLookup();
|
|
228
|
+
(globalThis as any).authorityLookup = authorityLookup;
|
|
229
|
+
|
|
230
|
+
const AuthorityLookupController = SocketFunction.register(
|
|
231
|
+
"AuthorityLookup-019d1bbd-e7b9-719b-8fd1-fe06c7e5b115",
|
|
232
|
+
authorityLookup,
|
|
233
|
+
() => ({
|
|
234
|
+
networkSyncPaths: {},
|
|
235
|
+
}),
|
|
236
|
+
() => ({
|
|
237
|
+
hooks: [requiresNetworkTrustHook],
|
|
238
|
+
})
|
|
239
|
+
);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { runInfinitePoll } from "socket-function/src/batching";
|
|
2
|
+
import { lazy } from "socket-function/src/caching";
|
|
3
|
+
import { binarySearchIndex } from "socket-function/src/misc";
|
|
4
|
+
import { registerResource } from "../diagnostics/trackResources";
|
|
5
|
+
import { MAX_CHANGE_AGE, Time, PathValue, byLockGroup, compareTime } from "./pathValueCore";
|
|
6
|
+
import { PathRouter } from "./PathRouter";
|
|
7
|
+
import { getInternalValueWatchPrefix, pathWatcher } from "./PathWatcher";
|
|
8
|
+
|
|
9
|
+
function isGoldenLock(time: number, now: number): boolean {
|
|
10
|
+
return time < now - MAX_CHANGE_AGE * 2;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getLockWatcher2NodeId() {
|
|
14
|
+
return getInternalValueWatchPrefix() + "_LockWatcher2";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Anything over a certain age gets removed as we assume at that point the valid state won't change, and so we don't need to keep track of the watchers.
|
|
19
|
+
*/
|
|
20
|
+
class LockWatcher2 {
|
|
21
|
+
|
|
22
|
+
private remoteWatches = registerResource("paths|remoteWatches", new Map<string, {
|
|
23
|
+
newestTime: number;
|
|
24
|
+
}>());
|
|
25
|
+
|
|
26
|
+
// path => sorted by endTime
|
|
27
|
+
private validRangeWatchers = registerResource("paths|validRangeWatchers", new Map<string, {
|
|
28
|
+
startTime: Time;
|
|
29
|
+
endTime: Time;
|
|
30
|
+
watchers: PathValue[];
|
|
31
|
+
}[]>());
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
private watchRemotePath(path: string, newestTime: number) {
|
|
36
|
+
let watch = this.remoteWatches.get(path);
|
|
37
|
+
if (!watch) {
|
|
38
|
+
watch = {
|
|
39
|
+
newestTime: 0,
|
|
40
|
+
};
|
|
41
|
+
this.remoteWatches.set(path, watch);
|
|
42
|
+
// So do we do deduping on the watchers? I think we do. We have to do our own little watch unwatch thing.
|
|
43
|
+
pathWatcher.watchPath({
|
|
44
|
+
nodeId: getLockWatcher2NodeId(),
|
|
45
|
+
paths: [path],
|
|
46
|
+
parentPaths: [],
|
|
47
|
+
// NOTE: Receiving the full history is inefficient, however, it doesn't matter. The history gets compressed fairly aggressively, So, there won't be many values. Also, this makes our keeping track of what we're watching simpler. So, it might be actually more efficient overall.
|
|
48
|
+
fullHistory: true,
|
|
49
|
+
noInitialTrigger: true,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (newestTime > watch.newestTime) {
|
|
53
|
+
watch.newestTime = newestTime;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/** Tracks a lookup so that you can find if any path value will change the valid state of another path value.
|
|
59
|
+
* - ALSO, for any value which is held remotely, watches its locks.
|
|
60
|
+
*/
|
|
61
|
+
public watchValueLocks(values: PathValue[], now: number) {
|
|
62
|
+
values = values.filter(x => PathRouter.isSelfAuthority(x.path));
|
|
63
|
+
if (values.length === 0) return;
|
|
64
|
+
this.ensureGarbageCollectLoop();
|
|
65
|
+
let lockGroups = byLockGroup(values);
|
|
66
|
+
for (let [locks, valueGroup] of lockGroups) {
|
|
67
|
+
locks = locks.filter(x => !isGoldenLock(x.endTime.time, now));
|
|
68
|
+
if (locks.length === 0) continue;
|
|
69
|
+
for (let lock of locks) {
|
|
70
|
+
if (!PathRouter.isSelfAuthority(lock.path)) {
|
|
71
|
+
this.watchRemotePath(lock.path, lock.endTime.time);
|
|
72
|
+
}
|
|
73
|
+
let rangeWatchers = this.validRangeWatchers.get(lock.path);
|
|
74
|
+
if (!rangeWatchers) {
|
|
75
|
+
rangeWatchers = [];
|
|
76
|
+
this.validRangeWatchers.set(lock.path, rangeWatchers);
|
|
77
|
+
}
|
|
78
|
+
let list = rangeWatchers;
|
|
79
|
+
let index = binarySearchIndex(list.length, i => compareTime(list[i].endTime, lock.endTime));
|
|
80
|
+
if (index < 0) index = ~index;
|
|
81
|
+
list.splice(index, 0, {
|
|
82
|
+
startTime: lock.startTime,
|
|
83
|
+
endTime: lock.endTime,
|
|
84
|
+
watchers: valueGroup,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Returns all the path values that might have their valid state changed by this change. If this change is an accepted value, then it's anything that's within the locking region for any of the read locks, or the read lock itself (presumably, your reads exist before you, so if they change, it probably isn't them being initially created, it's probably them being rejected, which will cause you to be rejected.)
|
|
91
|
+
*/
|
|
92
|
+
public getValuePathWatchers(config: {
|
|
93
|
+
path: string;
|
|
94
|
+
time: Time;
|
|
95
|
+
}): PathValue[] {
|
|
96
|
+
let result: PathValue[][] = [];
|
|
97
|
+
|
|
98
|
+
let rangeWatchers = this.validRangeWatchers.get(config.path);
|
|
99
|
+
if (rangeWatchers && rangeWatchers.length > 0) {
|
|
100
|
+
let list = rangeWatchers;
|
|
101
|
+
let index = binarySearchIndex(list.length, i => compareTime(list[i].endTime, config.time));
|
|
102
|
+
if (index < 0) index = ~index;
|
|
103
|
+
// Anything with an endTime < our time definitely won't overlap us.
|
|
104
|
+
for (let i = index; i < rangeWatchers.length; i++) {
|
|
105
|
+
let watcher = rangeWatchers[i];
|
|
106
|
+
if (compareTime(watcher.startTime, config.time) <= 0) {
|
|
107
|
+
result.push(watcher.watchers);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return result.flat();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private ensureGarbageCollectLoop = lazy(() => {
|
|
116
|
+
runInfinitePoll(MAX_CHANGE_AGE, () => this.garbageCollectOldWatchers());
|
|
117
|
+
});
|
|
118
|
+
private garbageCollectOldWatchers() {
|
|
119
|
+
let threshold = Date.now() - MAX_CHANGE_AGE * 2;
|
|
120
|
+
|
|
121
|
+
// 1) Look through remote watches, and any which are too old, we can actually unwatch their paths with Pathwatcher.
|
|
122
|
+
let pathsToUnwatch: string[] = [];
|
|
123
|
+
for (let [path, watch] of this.remoteWatches) {
|
|
124
|
+
if (watch.newestTime < threshold) {
|
|
125
|
+
pathsToUnwatch.push(path);
|
|
126
|
+
this.remoteWatches.delete(path);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (pathsToUnwatch.length > 0) {
|
|
130
|
+
pathWatcher.unwatchPath({
|
|
131
|
+
paths: pathsToUnwatch,
|
|
132
|
+
parentPaths: [],
|
|
133
|
+
callback: getLockWatcher2NodeId(),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 2) Look through validRangeWatchers, and remove any with an endTime that are too old
|
|
138
|
+
for (let [path, ranges] of this.validRangeWatchers) {
|
|
139
|
+
let index = binarySearchIndex(ranges.length, i => ranges[i].endTime.time - threshold);
|
|
140
|
+
if (index < 0) index = ~index;
|
|
141
|
+
if (index > 0) {
|
|
142
|
+
ranges.splice(0, index);
|
|
143
|
+
}
|
|
144
|
+
if (ranges.length === 0) {
|
|
145
|
+
this.validRangeWatchers.delete(path);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export const lockWatcher2 = new LockWatcher2();
|