@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.
- package/dist/fs-agent.d.ts +30 -0
- package/dist/fs-agent.js +51 -3
- package/package.json +1 -1
package/dist/fs-agent.d.ts
CHANGED
|
@@ -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
|
-
|
|
1633
|
-
|
|
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.`
|