querysub 0.441.0 → 0.443.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/bin/mcp-indexed-logs.js +6 -0
  3. package/package.json +7 -4
  4. package/spec.txt +1 -0
  5. package/src/-a-archives/archiveCache.ts +1 -1
  6. package/src/-e-certs/EdgeCertController.ts +2 -8
  7. package/src/-f-node-discovery/NodeDiscovery.ts +14 -7
  8. package/src/-g-core-values/NodeCapabilities.ts +4 -0
  9. package/src/0-path-value-core/AuthorityLookup.ts +9 -4
  10. package/src/0-path-value-core/LockWatcher2.ts +1 -0
  11. package/src/0-path-value-core/PathValueController.ts +1 -1
  12. package/src/0-path-value-core/PathWatcher.ts +17 -19
  13. package/src/0-path-value-core/pathValueArchives.ts +20 -2
  14. package/src/0-path-value-core/pathValueCore.ts +10 -4
  15. package/src/1-path-client/RemoteWatcher.ts +17 -22
  16. package/src/1-path-client/pathValueClientWatcher.ts +1 -1
  17. package/src/2-proxy/PathValueProxyWatcher.ts +5 -5
  18. package/src/3-path-functions/PathFunctionRunner.ts +1 -1
  19. package/src/4-querysub/Querysub.ts +24 -7
  20. package/src/4-querysub/QuerysubController.ts +9 -2
  21. package/src/archiveapps/archiveJoinEntry.ts +1 -1
  22. package/src/diagnostics/ValuePathWarning.tsx +68 -0
  23. package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +113 -1
  24. package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +4 -4
  25. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +37 -2
  26. package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogs.ts +389 -0
  27. package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts +190 -0
  28. package/src/diagnostics/logs/diskLogger.ts +3 -0
  29. package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +2 -1
  30. package/src/diagnostics/managementPages.tsx +5 -1
  31. package/src/library-components/SyncedController.ts +2 -1
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(Get-ChildItem *)",
5
+ "mcp__querysub-logs__searchIndexes"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ process.argv.push("--local");
4
+
5
+ require("typenode");
6
+ require("../src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.441.0",
3
+ "version": "0.443.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.23",
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
@@ -1,5 +1,6 @@
1
1
 
2
2
 
3
+
3
4
  Trigger values
4
5
  1) validStateComputer.ingestValuesAndValidStates
5
6
  1.5) authorityStorage.ingestValues
@@ -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 * 50,
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 selfNodeId = SocketFunction.mountedNodeId;
129
- let nodeObj = getNodeIdLocation(selfNodeId);
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.log(magenta(`Auditing node list`));
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.log(yellow(`Node ${nodeId} was found to be dead, removing from node list. Last heartbeat at ${formatDateTime(lastTime)}, dead threshold at ${formatDateTime(deadTime)}`));
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.log(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}`));
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 runMainSyncLoops() {
457
- await syncArchives();
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(timeInSecond * 5, errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].addNode(getOwnNodeId())));
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
- runMainSyncLoops().catch(e => {
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
- await onNodeBroadcasted();
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 {
@@ -131,6 +131,7 @@ class LockWatcher2 {
131
131
  paths: pathsToUnwatch,
132
132
  parentPaths: [],
133
133
  callback: getLockWatcher2NodeId(),
134
+ reason: "LockWatcher2 cleanup",
134
135
  });
135
136
  }
136
137
 
@@ -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
- // Wait while, so Querysub doesn't thrash when clients connect and disconnect
508
- // TODO: We should be smarter about our wait time, waiting less if we have a lot of resource
509
- // pressure, and more (potentially forever), if we don't have much resource pressure
510
- // (memory, cpu, and network).
511
- setTimeout(() => {
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
- for (let dataFile of dataPaths) {
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[];
@@ -4,7 +4,7 @@ import { addEpsilons, minusEpsilon } from "../bits";
4
4
  import { logErrors } from "../errors";
5
5
  import { appendToPathStr, getParentPathStr, getPathDepth, getPathIndexAssert, hack_getPackedPathSuffix, hack_setPackedPathSuffix, hack_stripPackedPath } from "../path";
6
6
  import { measureFnc, measureWrap } from "socket-function/src/profiling/measure";
7
- import { IdentityController_getOwnPubKeyShort } from "../-c-identity/IdentityController";
7
+ import { IdentityController_getOwnPubKeyShort, debugNodeId, debugNodeThread } from "../-c-identity/IdentityController";
8
8
  import { pathValueArchives } from "./pathValueArchives";
9
9
  import { blue } from "socket-function/src/formatting/logColors";
10
10
  import { delay, runInfinitePoll } from "socket-function/src/batching";
@@ -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
- if (isDiskAudit()) {
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);
@@ -693,6 +694,10 @@ class AuthorityPathValueStorage {
693
694
 
694
695
  public addParentSyncs(parentSyncs: { parentPath: string; sourceNodeId: string }[]) {
695
696
  for (let obj of parentSyncs) {
697
+ if (isDebugLogEnabled()) {
698
+ auditLog("RECEIVED PARENT PATH", { parentPath: obj.parentPath, remoteNodeId: debugNodeId(obj.sourceNodeId), remoteNodeThreadId: debugNodeThread(obj.sourceNodeId) });
699
+ }
700
+
696
701
  let decoded = decodeParentFilter(obj.parentPath);
697
702
  let range = decoded ? { start: decoded.start, end: decoded.end } : { start: 0, end: 1 };
698
703
  let parentPath = hack_stripPackedPath(obj.parentPath);
@@ -1063,6 +1068,7 @@ class AuthorityPathValueStorage {
1063
1068
 
1064
1069
  if (pathsToClear.size > 0) {
1065
1070
  for (let path of pathsToClear) {
1071
+ console.info(`Destroying event path`, { path });
1066
1072
  this.destroyPath(path);
1067
1073
  // I'm not sure if removing it as a parent is needed, or... maybe it is needed,
1068
1074
  // 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
- if (isDevDebugbreak()) {
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 });
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
- if (isDevDebugbreak()) {
480
- for (let path of paths) {
481
- auditLog("remoteWatcher inner WATCH", { path, remoteNodeId: authorityId });
482
- }
483
- for (let path of parentPaths) {
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
- if (isDevDebugbreak()) {
589
- for (let path of paths) {
590
- auditLog("remoteWatcher inner UNWATCH", { path, remoteNodeId: authorityIdBase });
591
- }
592
- for (let path of parentPaths) {
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;
@@ -453,7 +453,7 @@ export class PathFunctionRunner {
453
453
  let fraction = getRoutingOverridePart(callPath.CallId)?.route;
454
454
  if (fraction === undefined) {
455
455
  fraction = PathRouter.getSingleKeyRoute(callPath.CallId);
456
- console.error(`No routing override found for callId, falling back to direct hash`, { callId: callPath.CallId, fraction });
456
+ console.error(`No routing override found for callId, falling back to direct hash`, { callId: callPath.CallId, fraction, domainName: callPath.DomainName, moduleId: callPath.ModuleId, functionId: callPath.FunctionId, callerMachineId: callPath.callerMachineId, callerIP: callPath.callerIP });
457
457
  }
458
458
  // It isn't secondary if it is primary
459
459
  if (shardRange.startFraction <= fraction && fraction < shardRange.endFraction) return 0;
@@ -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
- await SocketFunction.mount({
1068
+ let mountPromise = SocketFunction.mount({
1055
1069
  public: isPublic(),
1056
1070
  port,
1057
1071
  ...await getThreadKeyCert(),
1058
1072
  });
1059
1073
 
1060
- await publishMachineARecords();
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
- await Querysub.optionalStartupWait();
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";