@rljson/fs-agent 0.0.58 → 0.0.60
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 +44 -0
- package/dist/fs-agent.js +60 -3
- package/package.json +1 -1
package/dist/fs-agent.d.ts
CHANGED
|
@@ -176,6 +176,30 @@ 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;
|
|
193
|
+
/**
|
|
194
|
+
* How many of its own past states a node remembers.
|
|
195
|
+
*
|
|
196
|
+
* Used to recognise a sender that is BEHIND this node — one that declares a
|
|
197
|
+
* state this node has held and left. A burst on a large folder produces a few
|
|
198
|
+
* dozen states, so this is generous; forgetting the oldest costs only the
|
|
199
|
+
* ability to spot a very stale sender, which the mass-delete guard still
|
|
200
|
+
* catches.
|
|
201
|
+
*/
|
|
202
|
+
export declare const STATE_HISTORY_MAX = 1000;
|
|
179
203
|
/**
|
|
180
204
|
* What an agent concluded about an inbound ref.
|
|
181
205
|
*
|
|
@@ -276,6 +300,15 @@ export declare class FsAgent {
|
|
|
276
300
|
/** Files written vs left alone by the current {@link restore}. */
|
|
277
301
|
private _restoreWritten;
|
|
278
302
|
private _restoreSkipped;
|
|
303
|
+
/**
|
|
304
|
+
* The states this node has held, oldest first.
|
|
305
|
+
*
|
|
306
|
+
* A push declaring one of these — but not the current one — comes from a
|
|
307
|
+
* sender that is behind this node. Applying such a tree is what puts a
|
|
308
|
+
* deleted file back, so it is ignored outright rather than applied
|
|
309
|
+
* additively.
|
|
310
|
+
*/
|
|
311
|
+
private readonly _stateHistory;
|
|
279
312
|
/**
|
|
280
313
|
* Absolute paths of the files this agent has actually TOLD anyone about.
|
|
281
314
|
*
|
|
@@ -739,6 +772,17 @@ export declare class FsAgent {
|
|
|
739
772
|
* its own pushes, and the states it adopts from theirs.
|
|
740
773
|
* @param tree - The tree that just went out, or was adopted as ours.
|
|
741
774
|
*/
|
|
775
|
+
/**
|
|
776
|
+
* Records a state this node is now in.
|
|
777
|
+
* @param ref - The content ref of that state.
|
|
778
|
+
*/
|
|
779
|
+
private _rememberState;
|
|
780
|
+
/**
|
|
781
|
+
* Whether a push comes from a sender this node has already moved past.
|
|
782
|
+
* @param predecessorRefs - What the push declares it descends from.
|
|
783
|
+
* @returns True when every declared parent is a state this node has left.
|
|
784
|
+
*/
|
|
785
|
+
private _senderIsBehind;
|
|
742
786
|
private _rememberAnnounced;
|
|
743
787
|
private _adoptAppliedRef;
|
|
744
788
|
/**
|
package/dist/fs-agent.js
CHANGED
|
@@ -1079,6 +1079,8 @@ 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;
|
|
1083
|
+
const STATE_HISTORY_MAX = 1e3;
|
|
1082
1084
|
const VERDICT_REASON = {
|
|
1083
1085
|
"own-echo": "is this agent's own last advertisement echoed back",
|
|
1084
1086
|
stale: "is not the newest its sender has advertised"
|
|
@@ -1152,6 +1154,15 @@ class FsAgent {
|
|
|
1152
1154
|
/** Files written vs left alone by the current {@link restore}. */
|
|
1153
1155
|
_restoreWritten = 0;
|
|
1154
1156
|
_restoreSkipped = 0;
|
|
1157
|
+
/**
|
|
1158
|
+
* The states this node has held, oldest first.
|
|
1159
|
+
*
|
|
1160
|
+
* A push declaring one of these — but not the current one — comes from a
|
|
1161
|
+
* sender that is behind this node. Applying such a tree is what puts a
|
|
1162
|
+
* deleted file back, so it is ignored outright rather than applied
|
|
1163
|
+
* additively.
|
|
1164
|
+
*/
|
|
1165
|
+
_stateHistory = [];
|
|
1155
1166
|
/**
|
|
1156
1167
|
* Absolute paths of the files this agent has actually TOLD anyone about.
|
|
1157
1168
|
*
|
|
@@ -1267,6 +1278,7 @@ class FsAgent {
|
|
|
1267
1278
|
}
|
|
1268
1279
|
}
|
|
1269
1280
|
_persistCurrentRef(ref) {
|
|
1281
|
+
this._rememberState(ref);
|
|
1270
1282
|
try {
|
|
1271
1283
|
writeFileSync(
|
|
1272
1284
|
join(this._rootPath, AGENT_STATE_FILE),
|
|
@@ -1637,9 +1649,26 @@ ${err.stack}` : String(err);
|
|
|
1637
1649
|
const dirPath = meta.relativePath === "." ? targetPath : join(targetPath, meta.relativePath);
|
|
1638
1650
|
await mkdir(dirPath, { recursive: true });
|
|
1639
1651
|
if (treeNode.children && Array.isArray(treeNode.children)) {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1652
|
+
const children = [...treeNode.children];
|
|
1653
|
+
let next = 0;
|
|
1654
|
+
const worker = async () => {
|
|
1655
|
+
for (; ; ) {
|
|
1656
|
+
const index = next++;
|
|
1657
|
+
if (index >= children.length) return;
|
|
1658
|
+
await this._restoreTree(
|
|
1659
|
+
children[index],
|
|
1660
|
+
trees,
|
|
1661
|
+
targetPath,
|
|
1662
|
+
isOwnRoot
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
await Promise.all(
|
|
1667
|
+
Array.from(
|
|
1668
|
+
{ length: Math.min(RESTORE_FETCH_CONCURRENCY, children.length) },
|
|
1669
|
+
worker
|
|
1670
|
+
)
|
|
1671
|
+
);
|
|
1643
1672
|
}
|
|
1644
1673
|
}
|
|
1645
1674
|
}
|
|
@@ -2360,6 +2389,28 @@ ${err.stack}` : String(err);
|
|
|
2360
2389
|
* its own pushes, and the states it adopts from theirs.
|
|
2361
2390
|
* @param tree - The tree that just went out, or was adopted as ours.
|
|
2362
2391
|
*/
|
|
2392
|
+
/**
|
|
2393
|
+
* Records a state this node is now in.
|
|
2394
|
+
* @param ref - The content ref of that state.
|
|
2395
|
+
*/
|
|
2396
|
+
_rememberState(ref) {
|
|
2397
|
+
if (this._stateHistory.includes(ref)) return;
|
|
2398
|
+
this._stateHistory.push(ref);
|
|
2399
|
+
if (this._stateHistory.length > STATE_HISTORY_MAX) {
|
|
2400
|
+
this._stateHistory.shift();
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Whether a push comes from a sender this node has already moved past.
|
|
2405
|
+
* @param predecessorRefs - What the push declares it descends from.
|
|
2406
|
+
* @returns True when every declared parent is a state this node has left.
|
|
2407
|
+
*/
|
|
2408
|
+
_senderIsBehind(predecessorRefs) {
|
|
2409
|
+
if (predecessorRefs.length === 0) return false;
|
|
2410
|
+
return predecessorRefs.every(
|
|
2411
|
+
(ref) => ref !== this._currentRef && ref !== this._lastAppliedRef && this._stateHistory.includes(ref)
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2363
2414
|
_rememberAnnounced(tree) {
|
|
2364
2415
|
this._announcedFiles.clear();
|
|
2365
2416
|
for (const path of this._getFileContentMap(tree).keys()) {
|
|
@@ -2531,6 +2582,12 @@ ${err.stack}` : String(err);
|
|
|
2531
2582
|
(r) => r !== void 0
|
|
2532
2583
|
);
|
|
2533
2584
|
const senderSawMyState = !ancestryIsCarried || !declaresAncestry || statesIAmIn.some((r) => predecessorRefs.includes(r));
|
|
2585
|
+
if (ancestryIsCarried && declaresAncestry && this._senderIsBehind(predecessorRefs)) {
|
|
2586
|
+
console.warn(
|
|
2587
|
+
`[FsAgent] ref=${treeRef.slice(0, 8)}… descends from a state this node has already left — ignoring, the sender will catch up and re-push.`
|
|
2588
|
+
);
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2534
2591
|
const withholdPrune = restoreOptions?.cleanTarget && (ancestryExpected && !declaresAncestry || !senderSawMyState);
|
|
2535
2592
|
const applyOptions = withholdPrune ? { ...restoreOptions, cleanTarget: false } : restoreOptions;
|
|
2536
2593
|
if (applyOptions !== restoreOptions) {
|