@rljson/fs-agent 0.0.57 → 0.0.59

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.
@@ -176,6 +176,20 @@ export declare const TREE_FETCH_CONCURRENCY = 64;
176
176
  * which is the cheap half of the trade.
177
177
  */
178
178
  export declare const RESTORED_BLOB_MEMORY_MAX = 50000;
179
+ /**
180
+ * How many tree children a restore works on at once.
181
+ *
182
+ * A restore's cost is dominated by `getBlob`, and a blob this node does not
183
+ * already hold is a socket round trip. Walking the tree strictly sequentially
184
+ * makes the whole restore `files × RTT` — invisible on localhost, and about
185
+ * sixteen files a second on the lab, where a 1 200-file folder then takes
186
+ * minutes and reads as a stalled node.
187
+ *
188
+ * Sixteen rather than sixty-four: blobs carry file CONTENT, so the ceiling is
189
+ * memory in flight rather than request count, and a folder of large files at
190
+ * high concurrency is how a client runs out of heap.
191
+ */
192
+ export declare const RESTORE_FETCH_CONCURRENCY = 16;
179
193
  /**
180
194
  * What an agent concluded about an inbound ref.
181
195
  *
@@ -276,6 +290,14 @@ export declare class FsAgent {
276
290
  /** Files written vs left alone by the current {@link restore}. */
277
291
  private _restoreWritten;
278
292
  private _restoreSkipped;
293
+ /**
294
+ * Absolute paths of the files this agent has actually TOLD anyone about.
295
+ *
296
+ * A prune may only remove a file that peers could know exists. Anything
297
+ * written since the last announcement is invisible to every sender, so a
298
+ * tree that lacks it is not deleting it — it simply predates it.
299
+ */
300
+ private readonly _announcedFiles;
279
301
  /**
280
302
  * Files this restore DELETED.
281
303
  *
@@ -724,6 +746,14 @@ export declare class FsAgent {
724
746
  * @returns `apply`, or the reason it is not news.
725
747
  */
726
748
  private _inboundRefVerdict;
749
+ /**
750
+ * Records the files an announcement carried.
751
+ *
752
+ * Called wherever this agent commits to a state as "what peers know" —
753
+ * its own pushes, and the states it adopts from theirs.
754
+ * @param tree - The tree that just went out, or was adopted as ours.
755
+ */
756
+ private _rememberAnnounced;
727
757
  private _adoptAppliedRef;
728
758
  /**
729
759
  * Derives a deterministic content key from an FsTree.
package/dist/fs-agent.js CHANGED
@@ -1079,6 +1079,7 @@ const AGENT_STATE_FILE = ".fsagent-state.json";
1079
1079
  const REFUSAL_ANSWER_COOLDOWN_MS = 5e3;
1080
1080
  const TREE_FETCH_CONCURRENCY = 64;
1081
1081
  const RESTORED_BLOB_MEMORY_MAX = 5e4;
1082
+ const RESTORE_FETCH_CONCURRENCY = 16;
1082
1083
  const VERDICT_REASON = {
1083
1084
  "own-echo": "is this agent's own last advertisement echoed back",
1084
1085
  stale: "is not the newest its sender has advertised"
@@ -1152,6 +1153,14 @@ class FsAgent {
1152
1153
  /** Files written vs left alone by the current {@link restore}. */
1153
1154
  _restoreWritten = 0;
1154
1155
  _restoreSkipped = 0;
1156
+ /**
1157
+ * Absolute paths of the files this agent has actually TOLD anyone about.
1158
+ *
1159
+ * A prune may only remove a file that peers could know exists. Anything
1160
+ * written since the last announcement is invisible to every sender, so a
1161
+ * tree that lacks it is not deleting it — it simply predates it.
1162
+ */
1163
+ _announcedFiles = /* @__PURE__ */ new Set();
1155
1164
  /**
1156
1165
  * Files this restore DELETED.
1157
1166
  *
@@ -1629,9 +1638,26 @@ ${err.stack}` : String(err);
1629
1638
  const dirPath = meta.relativePath === "." ? targetPath : join(targetPath, meta.relativePath);
1630
1639
  await mkdir(dirPath, { recursive: true });
1631
1640
  if (treeNode.children && Array.isArray(treeNode.children)) {
1632
- for (const childHash of treeNode.children) {
1633
- await this._restoreTree(childHash, trees, targetPath, isOwnRoot);
1634
- }
1641
+ const children = [...treeNode.children];
1642
+ let next = 0;
1643
+ const worker = async () => {
1644
+ for (; ; ) {
1645
+ const index = next++;
1646
+ if (index >= children.length) return;
1647
+ await this._restoreTree(
1648
+ children[index],
1649
+ trees,
1650
+ targetPath,
1651
+ isOwnRoot
1652
+ );
1653
+ }
1654
+ };
1655
+ await Promise.all(
1656
+ Array.from(
1657
+ { length: Math.min(RESTORE_FETCH_CONCURRENCY, children.length) },
1658
+ worker
1659
+ )
1660
+ );
1635
1661
  }
1636
1662
  }
1637
1663
  }
@@ -1939,6 +1965,9 @@ ${err.stack}` : String(err);
1939
1965
  }
1940
1966
  }
1941
1967
  } else if (!expectedFiles.has(fullPath) && preRestore.has(fullPath)) {
1968
+ if (this._announcedFiles.size > 0 && !this._announcedFiles.has(fullPath)) {
1969
+ continue;
1970
+ }
1942
1971
  await rm(fullPath, { force: true });
1943
1972
  this._restorePruned++;
1944
1973
  }
@@ -1988,12 +2017,14 @@ ${err.stack}` : String(err);
1988
2017
  this._currentRef = initialRef;
1989
2018
  this._persistCurrentRef(initialRef);
1990
2019
  this._lastSentContentKey = this._contentKeyFromTree(initialTree);
2020
+ this._rememberAnnounced(initialTree);
1991
2021
  }
1992
2022
  if (initialRef && !isSilentJoiner) {
1993
2023
  this._lastSentRef = initialRef;
1994
2024
  this._currentRef = initialRef;
1995
2025
  this._persistCurrentRef(initialRef);
1996
2026
  this._lastSentContentKey = this._contentKeyFromTree(initialTree);
2027
+ this._rememberAnnounced(initialTree);
1997
2028
  await this._sendRef(
1998
2029
  connector,
1999
2030
  initialRef,
@@ -2073,6 +2104,7 @@ ${err.stack}` : String(err);
2073
2104
  }
2074
2105
  this._lastSentRef = ref;
2075
2106
  this._lastSentContentKey = contentKey;
2107
+ this._rememberAnnounced(tree);
2076
2108
  if (parentRef && parentRef !== ref) {
2077
2109
  connector.invalidateReceived(parentRef);
2078
2110
  }
@@ -2339,6 +2371,19 @@ ${err.stack}` : String(err);
2339
2371
  if (!isNewestFromSender) return "stale";
2340
2372
  return "apply";
2341
2373
  }
2374
+ /**
2375
+ * Records the files an announcement carried.
2376
+ *
2377
+ * Called wherever this agent commits to a state as "what peers know" —
2378
+ * its own pushes, and the states it adopts from theirs.
2379
+ * @param tree - The tree that just went out, or was adopted as ours.
2380
+ */
2381
+ _rememberAnnounced(tree) {
2382
+ this._announcedFiles.clear();
2383
+ for (const path of this._getFileContentMap(tree).keys()) {
2384
+ this._announcedFiles.add(join(this._rootPath, path));
2385
+ }
2386
+ }
2342
2387
  _adoptAppliedRef(connector, treeRef) {
2343
2388
  if (this._lastAppliedRef && this._lastAppliedRef !== treeRef) {
2344
2389
  connector.invalidateSent?.(this._lastAppliedRef);
@@ -2408,6 +2453,7 @@ ${err.stack}` : String(err);
2408
2453
  const ref = await dbAdapter.storeFsTree(tree, { previous });
2409
2454
  this._lastSentRef = ref;
2410
2455
  this._lastSentContentKey = this._contentKeyFromTree(tree);
2456
+ this._rememberAnnounced(tree);
2411
2457
  this._currentRef = ref;
2412
2458
  return ref;
2413
2459
  }
@@ -2471,6 +2517,7 @@ ${err.stack}` : String(err);
2471
2517
  this._currentRef = treeRef;
2472
2518
  this._persistCurrentRef(treeRef);
2473
2519
  this._lastSentContentKey = this._contentKeyFromTree(currentTree);
2520
+ this._rememberAnnounced(currentTree);
2474
2521
  return;
2475
2522
  }
2476
2523
  if (this._resolveConflicts && this._currentRef && predecessorRefs && predecessorRefs.length > 0) {
@@ -2545,6 +2592,7 @@ ${err.stack}` : String(err);
2545
2592
  if (!hasNewsOfOurOwn) {
2546
2593
  this._lastSentRef = postRestoreRef;
2547
2594
  this._lastSentContentKey = this._contentKeyFromTree(postRestoreTree);
2595
+ this._rememberAnnounced(postRestoreTree);
2548
2596
  if (postRestoreFiles.size < incomingFiles.size) {
2549
2597
  console.log(
2550
2598
  `[FsAgent] ref=${treeRef.slice(0, 8)}… left this node behind (${postRestoreFiles.size} of ${incomingFiles.size} files) — not announcing a state that is catching up.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/fs-agent",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "description": "Rljson fs-agent description",
5
5
  "homepage": "https://github.com/rljson/fs-agent",
6
6
  "bugs": "https://github.com/rljson/fs-agent/issues",