querysub 0.441.0 → 0.442.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/.claude/settings.local.json +8 -0
- package/bin/mcp-indexed-logs.js +6 -0
- package/package.json +7 -4
- package/spec.txt +1 -0
- package/src/-a-archives/archiveCache.ts +1 -1
- package/src/-e-certs/EdgeCertController.ts +2 -8
- package/src/-f-node-discovery/NodeDiscovery.ts +14 -7
- package/src/-g-core-values/NodeCapabilities.ts +4 -0
- package/src/0-path-value-core/AuthorityLookup.ts +9 -4
- package/src/0-path-value-core/LockWatcher2.ts +1 -0
- package/src/0-path-value-core/PathValueController.ts +1 -1
- package/src/0-path-value-core/PathWatcher.ts +17 -19
- package/src/0-path-value-core/pathValueArchives.ts +20 -2
- package/src/0-path-value-core/pathValueCore.ts +5 -3
- package/src/1-path-client/RemoteWatcher.ts +17 -22
- package/src/1-path-client/pathValueClientWatcher.ts +1 -1
- package/src/2-proxy/PathValueProxyWatcher.ts +5 -5
- package/src/4-querysub/Querysub.ts +24 -7
- package/src/4-querysub/QuerysubController.ts +9 -2
- package/src/archiveapps/archiveJoinEntry.ts +1 -1
- package/src/diagnostics/ValuePathWarning.tsx +68 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +113 -1
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +4 -4
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +37 -2
- package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogs.ts +389 -0
- package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts +190 -0
- package/src/diagnostics/logs/diskLogger.ts +3 -0
- package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +2 -1
- package/src/diagnostics/managementPages.tsx +5 -1
- package/src/library-components/SyncedController.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.442.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"error-email": "yarn typenode ./src/diagnostics/logs/errorNotifications/errorDigestEntry.tsx",
|
|
20
20
|
"build-native": "cd src/diagnostics/logs/IndexedLogs && node-gyp rebuild",
|
|
21
21
|
"audit-imports": "yarn typenode ./src/diagnostics/auditImportViolations.ts",
|
|
22
|
-
"test": "yarn typenode ./test.ts"
|
|
22
|
+
"test": "yarn typenode ./test.ts",
|
|
23
|
+
"mcp": "yarn typenode ./src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts",
|
|
24
|
+
"mc": "yarn typenode ./src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts --cwd D:/repos/qs-cyoa/"
|
|
23
25
|
},
|
|
24
26
|
"bin": {
|
|
25
27
|
"deploy": "./bin/deploy.js",
|
|
@@ -41,7 +43,8 @@
|
|
|
41
43
|
"error-watch": "./bin/error-watch.js",
|
|
42
44
|
"error-watch-public": "./bin/error-watch-public.js",
|
|
43
45
|
"audit-imports": "./bin/audit-imports.js",
|
|
44
|
-
"audit-disk-values": "./bin/audit-disk-values.js"
|
|
46
|
+
"audit-disk-values": "./bin/audit-disk-values.js",
|
|
47
|
+
"mcp-indexed-logs": "./bin/mcp-indexed-logs.js"
|
|
45
48
|
},
|
|
46
49
|
"dependencies": {
|
|
47
50
|
"@types/fs-ext": "^2.0.3",
|
|
@@ -61,7 +64,7 @@
|
|
|
61
64
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
62
65
|
"pako": "^2.1.0",
|
|
63
66
|
"peggy": "^5.0.6",
|
|
64
|
-
"socket-function": "^1.1.
|
|
67
|
+
"socket-function": "^1.1.33",
|
|
65
68
|
"terser": "^5.31.0",
|
|
66
69
|
"typesafecss": "^0.29.0",
|
|
67
70
|
"yaml": "^2.5.0",
|
package/spec.txt
CHANGED
|
@@ -17,7 +17,7 @@ import { measureWrap } from "socket-function/src/profiling/measure";
|
|
|
17
17
|
|
|
18
18
|
const SIZE_LIMIT = new SizeLimiter({
|
|
19
19
|
diskRoot: getStorageDir(),
|
|
20
|
-
maxBytes: isPublic() ? 1024 * 1024 * 1024 * 250 : 1024 * 1024 * 1024 *
|
|
20
|
+
maxBytes: isPublic() ? 1024 * 1024 * 1024 * 250 : 1024 * 1024 * 1024 * 100,
|
|
21
21
|
// Anything less than this and we can't even load enough weights models for a single task
|
|
22
22
|
minBytes: 1024 * 1024 * 1024 * 8,
|
|
23
23
|
maxDiskFraction: 0.3,
|
|
@@ -119,16 +119,10 @@ async function EdgeCertController_watchHTTPSKeyCert(
|
|
|
119
119
|
|
|
120
120
|
/** NOTE: Any SocketFunction.mount calls should probably call this, otherwise they won't be able to be called. */
|
|
121
121
|
export async function publishMachineARecords() {
|
|
122
|
-
if (!SocketFunction.isMounted()) {
|
|
123
|
-
throw new Error(`Must mount before publishing machine A records`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
122
|
let ip = await getHostedIP();
|
|
127
123
|
|
|
128
|
-
let
|
|
129
|
-
|
|
130
|
-
if (!nodeObj) throw new Error(`Invalid nodeId ${selfNodeId}`);
|
|
131
|
-
let machineAddress = nodeObj.address.split(".").slice(1).join(".");
|
|
124
|
+
let machineAddress = getOwnMachineId() + "." + getDomain();
|
|
125
|
+
|
|
132
126
|
let prevMachineIP = await getRecords("A", machineAddress);
|
|
133
127
|
let ipDomain = await getIPDomain();
|
|
134
128
|
let promises: Promise<void>[] = [];
|
|
@@ -49,6 +49,8 @@ let DISK_AUDIT_RATE = timeInMinute * 15;
|
|
|
49
49
|
let API_AUDIT_RATE = timeInSecond * 30;
|
|
50
50
|
let API_AUDIT_COUNT = 12;
|
|
51
51
|
|
|
52
|
+
let FIRST_BROADCAST_TIMEOUT = timeInSecond * 5;
|
|
53
|
+
|
|
52
54
|
|
|
53
55
|
let DEAD_NODE_POLL_COOLDOWN = timeInMinute * 5;
|
|
54
56
|
|
|
@@ -66,6 +68,7 @@ export function isNodeDiscoveryLogging() {
|
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
function getAlternateNodeIds(nodeId: string): MaybePromise<string[] | undefined> {
|
|
71
|
+
// IMPORTANT! NEVER allow for reconnection of client ids. A lot of code depends on the fact that clients will reconnect with a new client node id when they disconnect!
|
|
69
72
|
let machineId = certs.getMachineId(nodeId);
|
|
70
73
|
if (machineId === getOwnMachineId()) {
|
|
71
74
|
let decoded = decodeNodeId(nodeId);
|
|
@@ -356,7 +359,7 @@ async function runHeartbeatAuditLoop() {
|
|
|
356
359
|
await runInfinitePollCallAtStart(CHECK_INTERVAL * 0.9, async () => {
|
|
357
360
|
if (shutdown) return;
|
|
358
361
|
// Wait a bit longer, to try to prevent all nodes from synchronizing their audit times.
|
|
359
|
-
console.
|
|
362
|
+
console.info(magenta(`Auditing node list`));
|
|
360
363
|
await delay(CHECK_INTERVAL * Math.random() * 0.1);
|
|
361
364
|
//console.log(magenta(`Auditing node list`));
|
|
362
365
|
|
|
@@ -378,11 +381,11 @@ async function runHeartbeatAuditLoop() {
|
|
|
378
381
|
deadCount.set(nodeId, count);
|
|
379
382
|
if (count >= DEAD_CHECK_COUNT) {
|
|
380
383
|
removedNodeIds.push(nodeId);
|
|
381
|
-
console.
|
|
384
|
+
console.info(yellow(`Node ${nodeId} was found to be dead, removing from node list. Last heartbeat at ${formatDateTime(lastTime)}, dead threshold at ${formatDateTime(deadTime)}`));
|
|
382
385
|
await archives().del(nodeId);
|
|
383
386
|
deadCount.delete(nodeId);
|
|
384
387
|
} else {
|
|
385
|
-
console.
|
|
388
|
+
console.info(yellow(`Node ${nodeId} was found to be dead, last heartbeat at ${formatDateTime(lastTime)} < dead threshold at ${formatDateTime(deadTime)}, dead count ${count}/${DEAD_CHECK_COUNT}. Total nodes seen ${nodeIds.length}`));
|
|
386
389
|
pendingDeadCount++;
|
|
387
390
|
}
|
|
388
391
|
} else {
|
|
@@ -453,8 +456,12 @@ async function writeHeartbeat() {
|
|
|
453
456
|
await archives().set(nodeId, Buffer.from(now + ""));
|
|
454
457
|
}
|
|
455
458
|
|
|
456
|
-
async function
|
|
457
|
-
await
|
|
459
|
+
async function runServerSyncLoops() {
|
|
460
|
+
await Promise.all([
|
|
461
|
+
syncArchives(),
|
|
462
|
+
// NOTE: This is new, but I am pretty sure required. We don't want our node out there before we are trusted.
|
|
463
|
+
ensureWeAreTrusted(),
|
|
464
|
+
]);
|
|
458
465
|
|
|
459
466
|
discoveryReady.resolve();
|
|
460
467
|
|
|
@@ -473,7 +480,7 @@ async function runMainSyncLoops() {
|
|
|
473
480
|
if (isOwnNodeId(nodeId)) return;
|
|
474
481
|
// Ignore errors, but wait a bit, so hopefully 99.99% of the time we can be certain
|
|
475
482
|
// all other nodes know our node id at this point.
|
|
476
|
-
await timeoutToUndefinedSilent(
|
|
483
|
+
await timeoutToUndefinedSilent(FIRST_BROADCAST_TIMEOUT, errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].addNode(getOwnNodeId())));
|
|
477
484
|
}));
|
|
478
485
|
|
|
479
486
|
nodeBroadcasted.resolve();
|
|
@@ -527,7 +534,7 @@ if (isServer()) {
|
|
|
527
534
|
logErrors(runMemoryAuditLoop());
|
|
528
535
|
// NOTE: We used to wait until we mounted, but... we should be able to find nodes
|
|
529
536
|
// before we mount, right? (And what if we never mount?)
|
|
530
|
-
|
|
537
|
+
runServerSyncLoops().catch(e => {
|
|
531
538
|
discoveryReady.reject(e);
|
|
532
539
|
logErrors(Promise.reject(e));
|
|
533
540
|
});
|
|
@@ -139,6 +139,9 @@ class NodeCapabilitiesControllerBase {
|
|
|
139
139
|
public async getMemoryUsage() {
|
|
140
140
|
return process.memoryUsage();
|
|
141
141
|
}
|
|
142
|
+
public async getProcessId() {
|
|
143
|
+
return process.pid;
|
|
144
|
+
}
|
|
142
145
|
public async getFunctionRunnerShards() {
|
|
143
146
|
return getFunctionRunnerShards();
|
|
144
147
|
}
|
|
@@ -189,6 +192,7 @@ export const NodeCapabilitiesController = SocketFunction.register(
|
|
|
189
192
|
getEntryPoint: {},
|
|
190
193
|
getStartupTime: {},
|
|
191
194
|
getMemoryUsage: {},
|
|
195
|
+
getProcessId: {},
|
|
192
196
|
getFunctionRunnerShards: {},
|
|
193
197
|
getTrueTimeOffset: {},
|
|
194
198
|
getInspectURL: { hooks: [requiresNetworkTrustHook] },
|
|
@@ -6,7 +6,7 @@ import { getDomain, isPublic } from "../config";
|
|
|
6
6
|
import { cache, lazy } from "socket-function/src/caching";
|
|
7
7
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
8
8
|
import { delay, runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
9
|
-
import { getAllNodeIds, getOwnNodeId, isOwnNodeId, onNodeBroadcasted, syncNodesNow, watchDeltaNodeIds, watchNodeIds } from "../-f-node-discovery/NodeDiscovery";
|
|
9
|
+
import { getAllNodeIds, getOwnNodeId, isOwnNodeId, onNodeBroadcasted, onNodeDiscoveryReady, syncNodesNow, watchDeltaNodeIds, watchNodeIds } from "../-f-node-discovery/NodeDiscovery";
|
|
10
10
|
import { IdentityController_getCurrentReconnectNodeIdAssert } from "../-c-identity/IdentityController";
|
|
11
11
|
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
12
12
|
import { isClient } from "../config2";
|
|
@@ -106,7 +106,9 @@ class AuthorityLookup {
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
public startSyncing = lazy(async () => {
|
|
109
|
-
|
|
109
|
+
// NOTE: This used to wait for onNodeBroadcasted. I know we had a reason at some point, but that reason might have went away after refactoring PathRouter. Waiting for node discovery is much faster. So hopefully we can leave this here because fast startup really helps development. If required we can probably do a check for is public and then do the faster case.
|
|
110
|
+
await onNodeDiscoveryReady();
|
|
111
|
+
|
|
110
112
|
let firstSync = true;
|
|
111
113
|
let promises: Promise<void>[] = [];
|
|
112
114
|
watchDeltaNodeIds(({ newNodeIds, removedNodeIds }) => {
|
|
@@ -168,6 +170,7 @@ class AuthorityLookup {
|
|
|
168
170
|
private connectTo = cache((nodeId: string) => {
|
|
169
171
|
let stopObj = { stop: false };
|
|
170
172
|
let promise = runInfinitePollCallAtStart(NETWORK_POLL_INTERVAL, async () => {
|
|
173
|
+
// NOTE: We keep calling even on disconnect, until it is removed from node discovery, so we handle reconnections. AND if it is removed from node discovery and reconnects, that's fine, it will tell us about the rediscovery as if was a new node, and we will setup it again.
|
|
171
174
|
await this.syncNodeSerial(nodeId)();
|
|
172
175
|
}, stopObj);
|
|
173
176
|
|
|
@@ -181,6 +184,7 @@ class AuthorityLookup {
|
|
|
181
184
|
});
|
|
182
185
|
|
|
183
186
|
|
|
187
|
+
// Only allow one outstanding call per node (but allow multiple if across different nodes)
|
|
184
188
|
private syncNodeSerial = cache((nodeId: string) => {
|
|
185
189
|
return runInSerial(async () => {
|
|
186
190
|
await this.syncNodeNow(nodeId);
|
|
@@ -188,10 +192,11 @@ class AuthorityLookup {
|
|
|
188
192
|
});
|
|
189
193
|
private disconnectWatch = cache((nodeId: string) => {
|
|
190
194
|
let onDisconnect = () => {
|
|
191
|
-
SocketFunction.onNextDisconnect(nodeId, onDisconnect);
|
|
195
|
+
SocketFunction.onNextDisconnect(nodeId, onDisconnect, "iKnowThatServerNodeIdsMayReconnect_andIHandleReconnections");
|
|
196
|
+
// NOTE: Will be re-added once we receive an updatePaths call (or the node will be removed by NodeDiscovery, in which case we will be entirely cleaned up).
|
|
192
197
|
this.topology.nodes.delete(nodeId);
|
|
193
198
|
};
|
|
194
|
-
SocketFunction.onNextDisconnect(nodeId, onDisconnect);
|
|
199
|
+
SocketFunction.onNextDisconnect(nodeId, onDisconnect, "iKnowThatServerNodeIdsMayReconnect_andIHandleReconnections");
|
|
195
200
|
});
|
|
196
201
|
private async syncNodeNow(nodeId: string) {
|
|
197
202
|
try {
|
|
@@ -240,7 +240,7 @@ export class PathValueControllerBase {
|
|
|
240
240
|
auditLog("UNWATCHING PARENT PATH", { path: value, sourceNodeId });
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
pathWatcher.unwatchPath({ paths: config.paths, parentPaths: config.parentPaths, callback: callerId });
|
|
243
|
+
pathWatcher.unwatchPath({ paths: config.paths, parentPaths: config.parentPaths, callback: callerId, reason: "PathValueController.unwatchLatest" });
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
public static async getInitialValues(config: {
|
|
@@ -2,7 +2,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
|
|
|
2
2
|
import { cache } from "socket-function/src/caching";
|
|
3
3
|
import { QueueLimited, keyByArray } from "socket-function/src/misc";
|
|
4
4
|
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
5
|
-
import { debugNodeThread } from "../-c-identity/IdentityController";
|
|
5
|
+
import { debugNodeId, debugNodeThread } from "../-c-identity/IdentityController";
|
|
6
6
|
import { isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
7
7
|
import { registerResource } from "../diagnostics/trackResources";
|
|
8
8
|
import { ignoreErrors } from "../errors";
|
|
@@ -14,6 +14,7 @@ import { WatchConfig, authorityStorage, PathValue, NodeId, compareTime, isCoreQu
|
|
|
14
14
|
import { matchesParentRangeFilter } from "./hackedPackedPathParentFiltering";
|
|
15
15
|
import { isClient } from "../config2";
|
|
16
16
|
import { delay } from "socket-function/src/batching";
|
|
17
|
+
import { isClientNodeId } from "socket-function/src/nodeCache";
|
|
17
18
|
|
|
18
19
|
setImmediate(() => import("../4-querysub/Querysub"));
|
|
19
20
|
|
|
@@ -216,8 +217,8 @@ class PathWatcher {
|
|
|
216
217
|
}
|
|
217
218
|
// NOTE: Automatically unwatches from remoteWatcher on all paths that are fully unwatched
|
|
218
219
|
@measureFnc
|
|
219
|
-
public unwatchPath(config: WatchConfig & { callback: PathWatcherCallback; }) {
|
|
220
|
-
const { callback } = config;
|
|
220
|
+
public unwatchPath(config: WatchConfig & { callback: PathWatcherCallback; reason: string; }) {
|
|
221
|
+
const { callback, reason } = config;
|
|
221
222
|
|
|
222
223
|
let fullyUnwatched: WatchConfig = {
|
|
223
224
|
paths: [],
|
|
@@ -240,9 +241,9 @@ class PathWatcher {
|
|
|
240
241
|
obj.watchers.delete(callback);
|
|
241
242
|
|
|
242
243
|
if (isOwnNodeId(callback)) {
|
|
243
|
-
auditLog("local UNWATCH PARENT", { path });
|
|
244
|
+
auditLog("local UNWATCH PARENT", { path, reason });
|
|
244
245
|
} else {
|
|
245
|
-
auditLog("non-local UNWATCH PARENT", { path, watcher: callback });
|
|
246
|
+
auditLog("non-local UNWATCH PARENT", { path, watcher: callback, remoteNodeId: debugNodeId(callback), remoteNodeThreadId: debugNodeThread(callback), reason });
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
if (obj.watchers.size === 0) {
|
|
@@ -277,9 +278,9 @@ class PathWatcher {
|
|
|
277
278
|
watchers.watchers.delete(callback);
|
|
278
279
|
|
|
279
280
|
if (isOwnNodeId(callback)) {
|
|
280
|
-
auditLog("local UNWATCH VALUE", { path });
|
|
281
|
+
auditLog("local UNWATCH VALUE", { path, reason });
|
|
281
282
|
} else {
|
|
282
|
-
auditLog("non-local UNWATCH VALUE", { path, watcher: callback });
|
|
283
|
+
auditLog("non-local UNWATCH VALUE", { path, watcher: callback, reason });
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
if (watchers.watchers.size === 0) {
|
|
@@ -501,20 +502,17 @@ class PathWatcher {
|
|
|
501
502
|
private ensureUnwatchingOnDisconnect = cache((nodeId: string) => {
|
|
502
503
|
if (isOwnNodeId(nodeId)) return;
|
|
503
504
|
if (nodeId.startsWith(getInternalValueWatchPrefix())) return;
|
|
505
|
+
if (!isClientNodeId(nodeId)) {
|
|
506
|
+
console.error(`UNEXPECTED SERVER NODE ID ${nodeId} ON PATH WATCHER. We need to handle the reconnection case here!`);
|
|
507
|
+
}
|
|
504
508
|
SocketFunction.onNextDisconnect(nodeId, async () => {
|
|
505
509
|
this.ensureUnwatchingOnDisconnect.clear(nodeId);
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
let watches = this.watchersToPaths.get(nodeId);
|
|
513
|
-
this.watchersToPaths.delete(nodeId);
|
|
514
|
-
if (watches) {
|
|
515
|
-
this.unwatchPath({ paths: Array.from(watches.paths), parentPaths: Array.from(watches.parents), callback: nodeId });
|
|
516
|
-
}
|
|
517
|
-
}, MAX_CHANGE_AGE);
|
|
510
|
+
// NOTE: We used to wait here, but I think that's invalid. Client nodeIds will never reconnect, just unwatch now, as they will can never receive any values anyways.
|
|
511
|
+
let watches = this.watchersToPaths.get(nodeId);
|
|
512
|
+
this.watchersToPaths.delete(nodeId);
|
|
513
|
+
if (watches) {
|
|
514
|
+
this.unwatchPath({ paths: Array.from(watches.paths), parentPaths: Array.from(watches.parents), callback: nodeId, reason: "ensureUnwatchingOnDisconnect" });
|
|
515
|
+
}
|
|
518
516
|
});
|
|
519
517
|
});
|
|
520
518
|
|
|
@@ -14,6 +14,8 @@ import { devDebugbreak, isNoNetwork } from "../config";
|
|
|
14
14
|
import { wrapArchivesWithCache } from "../-a-archives/archiveCache";
|
|
15
15
|
import { AuthoritySpec, PathRouter, debugSpec } from "./PathRouter";
|
|
16
16
|
import { authorityLookup } from "./AuthorityLookup";
|
|
17
|
+
import { delay } from "socket-function/src/batching";
|
|
18
|
+
import { safeLoop } from "socket-function/src/batching";
|
|
17
19
|
|
|
18
20
|
export const archives = lazy(() => wrapArchivesWithCache(getArchives("path-values/")));
|
|
19
21
|
export const archivesLocks = lazy(() => getArchives("path-values-locks/"));
|
|
@@ -298,7 +300,7 @@ export class PathValueArchives {
|
|
|
298
300
|
let notMatched = 0;
|
|
299
301
|
let tooOldValues = 0;
|
|
300
302
|
|
|
301
|
-
|
|
303
|
+
await safeLoop({ data: dataPaths, name: blue("Load PathValues From Buffer") }, async dataFile => {
|
|
302
304
|
let data = readCache.get(dataFile);
|
|
303
305
|
if (!data) throw new Error(`Data file ${dataFile} not in lookup which we just checked?`);
|
|
304
306
|
totalSize += data.byteLength;
|
|
@@ -325,7 +327,7 @@ export class PathValueArchives {
|
|
|
325
327
|
}
|
|
326
328
|
}
|
|
327
329
|
}
|
|
328
|
-
}
|
|
330
|
+
});
|
|
329
331
|
|
|
330
332
|
let relevantCount = totalCount - notMatched;
|
|
331
333
|
parseTime = Date.now() - parseTime;
|
|
@@ -447,6 +449,22 @@ export class PathValueArchives {
|
|
|
447
449
|
});
|
|
448
450
|
}
|
|
449
451
|
|
|
452
|
+
private static valuePathCountCache: { time: number; count: Promise<number> } | undefined;
|
|
453
|
+
public static async getValuePathCount(): Promise<number> {
|
|
454
|
+
const ONE_HOUR = 60 * 60 * 1000;
|
|
455
|
+
let cache = PathValueArchives.valuePathCountCache;
|
|
456
|
+
if (cache && Date.now() - cache.time < ONE_HOUR) {
|
|
457
|
+
return cache.count;
|
|
458
|
+
}
|
|
459
|
+
let countPromise = (async () => {
|
|
460
|
+
let locker = await pathValueArchives.getArchiveLocker();
|
|
461
|
+
let allFiles = await locker.getAllValidFiles();
|
|
462
|
+
return allFiles.length;
|
|
463
|
+
})();
|
|
464
|
+
PathValueArchives.valuePathCountCache = { time: Date.now(), count: countPromise };
|
|
465
|
+
return countPromise;
|
|
466
|
+
}
|
|
467
|
+
|
|
450
468
|
@measureFnc
|
|
451
469
|
public async getValuePaths(authority: AuthoritySpec): Promise<{
|
|
452
470
|
pickedPaths: string[];
|
|
@@ -567,14 +567,15 @@ class AuthorityPathValueStorage {
|
|
|
567
567
|
if (this.DEBUG_UNWATCH) {
|
|
568
568
|
console.log(blue(`Unsyncing path at ${Date.now()}`), path);
|
|
569
569
|
}
|
|
570
|
-
|
|
571
|
-
auditLog("DESTROY PATH", { path });
|
|
572
|
-
}
|
|
570
|
+
auditLog("DESTROY PATH", { path });
|
|
573
571
|
|
|
574
572
|
this.isSyncedCache.delete(path);
|
|
575
573
|
this.removePathFromStorage(path, "unwatched");
|
|
576
574
|
}
|
|
577
575
|
public markParentPathAsUnwatched(path: string) {
|
|
576
|
+
if (this.parentsSynced.has(path)) {
|
|
577
|
+
console.info(`Unwatching path that is a parent synced path (fine, but might be an issue)`, { path });
|
|
578
|
+
}
|
|
578
579
|
// NOTE: I don't think we have to handle the case where we are the authority,
|
|
579
580
|
// because we don't delete any actual data here.
|
|
580
581
|
this.parentsSynced.delete(path);
|
|
@@ -1063,6 +1064,7 @@ class AuthorityPathValueStorage {
|
|
|
1063
1064
|
|
|
1064
1065
|
if (pathsToClear.size > 0) {
|
|
1065
1066
|
for (let path of pathsToClear) {
|
|
1067
|
+
console.info(`Destroying event path`, { path });
|
|
1066
1068
|
this.destroyPath(path);
|
|
1067
1069
|
// I'm not sure if removing it as a parent is needed, or... maybe it is needed,
|
|
1068
1070
|
// and this isn't enough of a check?
|
|
@@ -113,7 +113,8 @@ export class RemoteWatcher {
|
|
|
113
113
|
console.log(yellow(`Trying to find new authority for disconnected watches ${paths.length} paths and ${parentPaths.length} parent paths`));
|
|
114
114
|
logErrors(this.tryToReconnectNow());
|
|
115
115
|
})());
|
|
116
|
-
|
|
116
|
+
// Reconnection is our loop running again, maybe matching the same nodeId, and then using it again.
|
|
117
|
+
}, "iKnowThatServerNodeIdsMayReconnect_andIHandleReconnections");
|
|
117
118
|
});
|
|
118
119
|
|
|
119
120
|
// NOTE: Lower numbers mean more lag on disconnected values, but... faster reconnection. AND... if a node is disconnected,
|
|
@@ -410,13 +411,11 @@ export class RemoteWatcher {
|
|
|
410
411
|
// Only remote watch remotes!
|
|
411
412
|
if (isOwnNodeId(authorityId)) continue;
|
|
412
413
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
auditLog("remoteWatcher outer PARENT WATCH", { path, remoteNodeId: authorityId });
|
|
419
|
-
}
|
|
414
|
+
for (let path of paths) {
|
|
415
|
+
auditLog("remoteWatcher outer WATCH", { path, remoteNodeId: authorityId });
|
|
416
|
+
}
|
|
417
|
+
for (let path of parentPaths) {
|
|
418
|
+
auditLog("remoteWatcher outer PARENT WATCH", { path, remoteNodeId: authorityId });
|
|
420
419
|
}
|
|
421
420
|
|
|
422
421
|
if (STOP_KEYS_DOUBLE_SENDS) {
|
|
@@ -476,13 +475,11 @@ export class RemoteWatcher {
|
|
|
476
475
|
}
|
|
477
476
|
}
|
|
478
477
|
for (let [authorityId, { paths, parentPaths }] of byAuthority) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
auditLog("remoteWatcher inner WATCH PARENT", { path, remoteNodeId: authorityId });
|
|
485
|
-
}
|
|
478
|
+
for (let path of paths) {
|
|
479
|
+
auditLog("remoteWatcher inner WATCH", { path, remoteNodeId: authorityId });
|
|
480
|
+
}
|
|
481
|
+
for (let path of parentPaths) {
|
|
482
|
+
auditLog("remoteWatcher inner WATCH PARENT", { path, remoteNodeId: authorityId });
|
|
486
483
|
}
|
|
487
484
|
// NOTE: We log watches here because we want to log batched watches
|
|
488
485
|
ActionsHistory.OnNewWatches({ newPathsWatched: paths, newParentsWatched: parentPaths, debugName: batched[0].debugName, authorityId });
|
|
@@ -585,13 +582,11 @@ export class RemoteWatcher {
|
|
|
585
582
|
});
|
|
586
583
|
}
|
|
587
584
|
for (let [authorityIdBase, { paths, parentPaths }] of unwatchesPerAuthority.entries()) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
auditLog("remoteWatcher inner UNWATCH PARENT", { path, remoteNodeId: authorityIdBase });
|
|
594
|
-
}
|
|
585
|
+
for (let path of paths) {
|
|
586
|
+
auditLog("remoteWatcher inner UNWATCH", { path, remoteNodeId: authorityIdBase });
|
|
587
|
+
}
|
|
588
|
+
for (let path of parentPaths) {
|
|
589
|
+
auditLog("remoteWatcher inner UNWATCH PARENT", { path, remoteNodeId: authorityIdBase });
|
|
595
590
|
}
|
|
596
591
|
if (!isOwnNodeId(authorityIdBase)) {
|
|
597
592
|
logErrors(RemoteWatcher.REMOTE_UNWATCH_FUNCTION({ paths, parentPaths }, authorityIdBase));
|
|
@@ -543,7 +543,7 @@ export class ClientWatcher {
|
|
|
543
543
|
//console.log(red("Unwatching paths"), pathsToUnwatch, parentPathsToUnwatch, Date.now());
|
|
544
544
|
let unwatchConfig: WatchConfig = { paths: Array.from(pathsToUnwatch), parentPaths: Array.from(parentPathsToUnwatch), };
|
|
545
545
|
// pathWatcher unwatches remotely as well
|
|
546
|
-
pathWatcher.unwatchPath({ callback: getOwnNodeId(), ...unwatchConfig });
|
|
546
|
+
pathWatcher.unwatchPath({ callback: getOwnNodeId(), ...unwatchConfig, reason: "pathValueClientWatcher local unwatch" });
|
|
547
547
|
}
|
|
548
548
|
});
|
|
549
549
|
});
|
|
@@ -1451,7 +1451,7 @@ export class PathValueProxyWatcher {
|
|
|
1451
1451
|
if (watcher.disposed) return;
|
|
1452
1452
|
let remainingUnsynced = Array.from(watcher.lastUnsyncedAccesses).filter(x => !authorityStorage.isSynced(x));
|
|
1453
1453
|
let remainingUnsyncedParent = Array.from(watcher.lastUnsyncedParentAccesses).filter(x => !authorityStorage.isParentSynced(x));
|
|
1454
|
-
console.warn(red(`Did not sync ${watcher.debugName} after 30 seconds. Either we had a lot synchronous block, or we will never received the values we are waiting for.`), remainingUnsynced, remainingUnsyncedParent, watcher.options.watchFunction);
|
|
1454
|
+
console.warn(red(`Did not sync ${watcher.debugName} after 30 seconds. Either we had a lot synchronous block, or we will never received the values we are waiting for.`), { remainingUnsynced, remainingUnsyncedParent }, watcher.options.watchFunction);
|
|
1455
1455
|
}, 30 * 1000);
|
|
1456
1456
|
setTimeout(() => {
|
|
1457
1457
|
if (watcher.syncRunCount !== syncRunCount) return;
|
|
@@ -1471,11 +1471,11 @@ export class PathValueProxyWatcher {
|
|
|
1471
1471
|
let notWatchingUnsynced = reallyUnsyncedAccesses.filter(x => !remoteWatcher.debugIsWatchingPath(x));
|
|
1472
1472
|
let notWatchingUnsyncedParent = reallyUnsyncedParentAccesses.filter(x => !remoteWatcher.debugIsWatchingPath(x));
|
|
1473
1473
|
if (notWatchingUnsynced.length !== 0 || notWatchingUnsyncedParent.length !== 0) {
|
|
1474
|
-
console.error((`${red("WATCHER FAILED TO SYNC")} ${watcher.debugName} ${magenta("NOT REMOTE WATCHING REQUIRED PATHS")}. This means our sync or unsync (likely unsync) logic is broken, in remoteWatcher/clientWatcher. OR, there were no read nodes when we tried to sync (we don't handle missing read nodes correctly at the moment)`), notWatchingUnsynced, notWatchingUnsyncedParent, watcher.options.watchFunction);
|
|
1474
|
+
console.error((`${red("WATCHER FAILED TO SYNC")} ${watcher.debugName} ${magenta("NOT REMOTE WATCHING REQUIRED PATHS")}. This means our sync or unsync (likely unsync) logic is broken, in remoteWatcher/clientWatcher. OR, there were no read nodes when we tried to sync (we don't handle missing read nodes correctly at the moment)`), { notWatchingUnsynced, notWatchingUnsyncedParent }, watcher.options.watchFunction);
|
|
1475
1475
|
debugbreak(2);
|
|
1476
1476
|
debugger;
|
|
1477
1477
|
} else {
|
|
1478
|
-
console.error((`${red("WATCHER FAILED TO SYNC")} ${watcher.debugName} ${magenta("DID NOT RECEIVE PATH VALUES")}. This means PathValueServer is not responding to watches, either to specific paths, or for all paths`), reallyUnsyncedAccesses, reallyUnsyncedParentAccesses, watcher.options.watchFunction);
|
|
1478
|
+
console.error((`${red("WATCHER FAILED TO SYNC")} ${watcher.debugName} ${magenta("DID NOT RECEIVE PATH VALUES")}. This means PathValueServer is not responding to watches, either to specific paths, or for all paths`), { reallyUnsyncedAccesses, reallyUnsyncedParentAccesses }, watcher.options.watchFunction);
|
|
1479
1479
|
// debugbreak(2);
|
|
1480
1480
|
// debugger;
|
|
1481
1481
|
}
|
|
@@ -1484,7 +1484,7 @@ export class PathValueProxyWatcher {
|
|
|
1484
1484
|
debugbreak(2);
|
|
1485
1485
|
debugger;
|
|
1486
1486
|
} else {
|
|
1487
|
-
console.error((`${red("WATCHER FAILED TO SYNC")} ${watcher.debugName} ${magenta("DID NOT TRIGGER WATCHER")}. This means either ProxyWatcher is broken (and isn't triggering when it should, or isn't watching when it should), or ClientWatcher/PathWatcher are broken and are not properly informing callers of watchers.`), watcher.lastUnsyncedAccesses, watcher.lastUnsyncedParentAccesses, watcher.options.watchFunction);
|
|
1487
|
+
console.error((`${red("WATCHER FAILED TO SYNC")} ${watcher.debugName} ${magenta("DID NOT TRIGGER WATCHER")}. This means either ProxyWatcher is broken (and isn't triggering when it should, or isn't watching when it should), or ClientWatcher/PathWatcher are broken and are not properly informing callers of watchers.`), { lastUnsyncedAccesses: watcher.lastUnsyncedAccesses, lastUnsyncedParentAccesses: watcher.lastUnsyncedParentAccesses }, watcher.options.watchFunction);
|
|
1488
1488
|
debugbreak(2);
|
|
1489
1489
|
debugger;
|
|
1490
1490
|
}
|
|
@@ -1494,7 +1494,7 @@ export class PathValueProxyWatcher {
|
|
|
1494
1494
|
if (watcher.countSinceLastFullSync > 10) {
|
|
1495
1495
|
require("debugbreak")(2);
|
|
1496
1496
|
debugger;
|
|
1497
|
-
console.warn(`Watcher ${watcher.debugName} has been unsynced for ${watcher.countSinceLastFullSync} times. This is fine, but maybe optimize it. Why is it cascading?`, watcher.lastUnsyncedAccesses, watcher.lastUnsyncedParentAccesses, watcher.options.watchFunction);
|
|
1497
|
+
console.warn(`Watcher ${watcher.debugName} has been unsynced for ${watcher.countSinceLastFullSync} times. This is fine, but maybe optimize it. Why is it cascading?`, { lastUnsyncedAccesses: watcher.lastUnsyncedAccesses, lastUnsyncedParentAccesses: watcher.lastUnsyncedParentAccesses }, watcher.options.watchFunction);
|
|
1498
1498
|
}
|
|
1499
1499
|
if (watcher.countSinceLastFullSync > 500) {
|
|
1500
1500
|
debugger;
|
|
@@ -32,7 +32,7 @@ import { inlineNestedCalls, syncSchema } from "../3-path-functions/syncSchema";
|
|
|
32
32
|
import type { identityStorageKey, IdentityStorageType } from "../-a-auth/certs";
|
|
33
33
|
|
|
34
34
|
import { ExternalRenderClass, qreact } from "../4-dom/qreact";
|
|
35
|
-
import { configRootDiscoveryLocation, getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
35
|
+
import { configRootDiscoveryLocation, getOwnNodeId, onNodeBroadcasted, onNodeDiscoveryReady } from "../-f-node-discovery/NodeDiscovery";
|
|
36
36
|
import { pathValueCommitter } from "../0-path-value-core/PathValueCommitter";
|
|
37
37
|
import debugbreak from "debugbreak";
|
|
38
38
|
import { registerDynamicResource } from "../diagnostics/trackResources";
|
|
@@ -290,11 +290,16 @@ export class Querysub {
|
|
|
290
290
|
* isn't presently necessary in most serverside scripts.
|
|
291
291
|
*/
|
|
292
292
|
@measureFnc
|
|
293
|
-
public static async optionalStartupWait() {
|
|
293
|
+
public static async optionalStartupWait(addTime?: (name: string) => void) {
|
|
294
|
+
// Used by authorityLookup, pulled out here so timing is more clear.
|
|
295
|
+
await onNodeDiscoveryReady();
|
|
296
|
+
addTime?.("onNodeDiscoveryReady");
|
|
294
297
|
await authorityLookup.startSyncing();
|
|
298
|
+
addTime?.("authorityLookup.startSyncing");
|
|
295
299
|
await measureBlock(async () => {
|
|
296
300
|
await waitForFirstTimeSync();
|
|
297
301
|
}, "waitForFirstTimeSync");
|
|
302
|
+
addTime?.("waitForFirstTimeSync");
|
|
298
303
|
}
|
|
299
304
|
|
|
300
305
|
public static createWatcher(watcher: (obj: SyncWatcher) => void, options?: Partial<WatcherOptions<unknown>>): {
|
|
@@ -1049,18 +1054,30 @@ export class Querysub {
|
|
|
1049
1054
|
if (isClient()) {
|
|
1050
1055
|
throw new Error(`--client processes cannot host a service. Either stop passing --client and keep the process on the network and trusted, or stop calling hostServer and call Querysub.configRootDiscoveryLocation instead.`);
|
|
1051
1056
|
}
|
|
1057
|
+
let times: {
|
|
1058
|
+
name: string;
|
|
1059
|
+
duration: number
|
|
1060
|
+
}[] = [];
|
|
1061
|
+
let time = Date.now();
|
|
1062
|
+
function addTime(name: string) {
|
|
1063
|
+
times.push({ name, duration: Date.now() - time });
|
|
1064
|
+
time = Date.now();
|
|
1065
|
+
}
|
|
1052
1066
|
|
|
1053
1067
|
this.socketFunctionInit();
|
|
1054
|
-
|
|
1068
|
+
let mountPromise = SocketFunction.mount({
|
|
1055
1069
|
public: isPublic(),
|
|
1056
1070
|
port,
|
|
1057
1071
|
...await getThreadKeyCert(),
|
|
1058
1072
|
});
|
|
1059
1073
|
|
|
1060
|
-
|
|
1074
|
+
let publishPromise = publishMachineARecords();
|
|
1075
|
+
await Promise.all([mountPromise, publishPromise]);
|
|
1076
|
+
addTime("mount & publish a records");
|
|
1077
|
+
|
|
1078
|
+
await Querysub.optionalStartupWait(addTime);
|
|
1061
1079
|
|
|
1062
|
-
|
|
1063
|
-
console.log(magenta(`Started hosting service ${name}`));
|
|
1080
|
+
console.log(magenta(`Started hosting service ${name}`) + ` | ${times.map(t => `${blue(t.name)}: ${green(formatTime(t.duration))}`).join(" | ")}`);
|
|
1064
1081
|
}
|
|
1065
1082
|
|
|
1066
1083
|
/** Syncs keys AND values (as we won't return a key for a value that is undefined). */
|
|
@@ -1333,7 +1350,7 @@ import "../diagnostics/trackResources";
|
|
|
1333
1350
|
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
1334
1351
|
import { getCountPerPaint } from "../functional/onNextPaint";
|
|
1335
1352
|
import { addEpsilons } from "../bits";
|
|
1336
|
-
import { blue, magenta } from "socket-function/src/formatting/logColors";
|
|
1353
|
+
import { blue, green, magenta } from "socket-function/src/formatting/logColors";
|
|
1337
1354
|
import { MachineController } from "../deployManager/machineController";
|
|
1338
1355
|
import { getRecords, setRecord } from "../-b-authorities/dnsAuthority";
|
|
1339
1356
|
import { testTCPIsListening } from "socket-function/src/networking";
|