@rljson/fs-agent 0.0.31 → 0.0.32
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 +24 -0
- package/dist/fs-agent.js +31 -0
- package/package.json +1 -1
package/dist/fs-agent.d.ts
CHANGED
|
@@ -148,6 +148,15 @@ export declare const AGENT_STATE_FILE = ".fsagent-state.json";
|
|
|
148
148
|
* three-file folder is an ordinary edit, and a guard that blocked it would
|
|
149
149
|
* fire constantly on small trees and be turned off.
|
|
150
150
|
*/
|
|
151
|
+
/**
|
|
152
|
+
* How long an agent waits before answering another refusal.
|
|
153
|
+
*
|
|
154
|
+
* Two nodes can refuse each other — each holding files the other lacks — and an
|
|
155
|
+
* unthrottled answer to every refusal is a loop. Long enough that a genuine
|
|
156
|
+
* catch-up (a restore of the answered ref) completes inside it, short enough
|
|
157
|
+
* that a node joining an idle network is not left waiting.
|
|
158
|
+
*/
|
|
159
|
+
export declare const REFUSAL_ANSWER_COOLDOWN_MS = 5000;
|
|
151
160
|
export declare const MASS_DELETE_MIN_FILES = 100;
|
|
152
161
|
/**
|
|
153
162
|
* Above this share of the folder, a prune is treated as suspicious rather than
|
|
@@ -212,6 +221,8 @@ export declare class FsAgent {
|
|
|
212
221
|
* state can still reach this agent.
|
|
213
222
|
*/
|
|
214
223
|
private _lastAppliedRef?;
|
|
224
|
+
/** When this agent last answered a refusal, for the cooldown. */
|
|
225
|
+
private _lastRefusalAnswerMs;
|
|
215
226
|
/** Content fingerprint of the last tree we broadcasted (paths+blobIds) */
|
|
216
227
|
private _lastSentContentKey?;
|
|
217
228
|
/**
|
|
@@ -598,6 +609,19 @@ export declare class FsAgent {
|
|
|
598
609
|
* @param connector - Connector whose dedup sets to retire from.
|
|
599
610
|
* @param treeRef - The ref that now describes this folder.
|
|
600
611
|
*/
|
|
612
|
+
/**
|
|
613
|
+
* Re-announce this folder's current state after refusing an incoming tree.
|
|
614
|
+
*
|
|
615
|
+
* A refusal means the sender holds less than this node does, so it is the one
|
|
616
|
+
* that needs telling. Without this the network settles into a state that is
|
|
617
|
+
* stable and wrong: the sparse node cannot push, the full node has nothing
|
|
618
|
+
* new to push, and nothing moves until an unrelated edit happens somewhere.
|
|
619
|
+
*
|
|
620
|
+
* Rate-limited because two nodes can refuse each other — each holding files
|
|
621
|
+
* the other lacks — and an unthrottled answer to every refusal is a loop.
|
|
622
|
+
* @param connector - Connector to broadcast on.
|
|
623
|
+
*/
|
|
624
|
+
private _readvertiseAfterRefusal;
|
|
601
625
|
private _adoptAppliedRef;
|
|
602
626
|
/**
|
|
603
627
|
* Derives a deterministic content key from an FsTree.
|
package/dist/fs-agent.js
CHANGED
|
@@ -1027,6 +1027,7 @@ const DISCONNECT_PAUSE_MAX_MS = 3e4;
|
|
|
1027
1027
|
const SYNC_ERROR_FILE = ".sync-errors.log";
|
|
1028
1028
|
const ATOMIC_TMP_PREFIX = ".fsagent-tmp-";
|
|
1029
1029
|
const AGENT_STATE_FILE = ".fsagent-state.json";
|
|
1030
|
+
const REFUSAL_ANSWER_COOLDOWN_MS = 5e3;
|
|
1030
1031
|
const MASS_DELETE_MIN_FILES = 100;
|
|
1031
1032
|
const MASS_DELETE_MAX_RATIO = 0.3;
|
|
1032
1033
|
class RestoreIncompleteError extends Error {
|
|
@@ -1069,6 +1070,8 @@ class FsAgent {
|
|
|
1069
1070
|
* state can still reach this agent.
|
|
1070
1071
|
*/
|
|
1071
1072
|
_lastAppliedRef;
|
|
1073
|
+
/** When this agent last answered a refusal, for the cooldown. */
|
|
1074
|
+
_lastRefusalAnswerMs = 0;
|
|
1072
1075
|
/** Content fingerprint of the last tree we broadcasted (paths+blobIds) */
|
|
1073
1076
|
_lastSentContentKey;
|
|
1074
1077
|
/**
|
|
@@ -2106,6 +2109,33 @@ ${err.stack}` : String(err);
|
|
|
2106
2109
|
* @param connector - Connector whose dedup sets to retire from.
|
|
2107
2110
|
* @param treeRef - The ref that now describes this folder.
|
|
2108
2111
|
*/
|
|
2112
|
+
/**
|
|
2113
|
+
* Re-announce this folder's current state after refusing an incoming tree.
|
|
2114
|
+
*
|
|
2115
|
+
* A refusal means the sender holds less than this node does, so it is the one
|
|
2116
|
+
* that needs telling. Without this the network settles into a state that is
|
|
2117
|
+
* stable and wrong: the sparse node cannot push, the full node has nothing
|
|
2118
|
+
* new to push, and nothing moves until an unrelated edit happens somewhere.
|
|
2119
|
+
*
|
|
2120
|
+
* Rate-limited because two nodes can refuse each other — each holding files
|
|
2121
|
+
* the other lacks — and an unthrottled answer to every refusal is a loop.
|
|
2122
|
+
* @param connector - Connector to broadcast on.
|
|
2123
|
+
*/
|
|
2124
|
+
async _readvertiseAfterRefusal(connector) {
|
|
2125
|
+
const ref = this._currentRef;
|
|
2126
|
+
if (!ref) return;
|
|
2127
|
+
const now = Date.now();
|
|
2128
|
+
if (now - this._lastRefusalAnswerMs < REFUSAL_ANSWER_COOLDOWN_MS) return;
|
|
2129
|
+
this._lastRefusalAnswerMs = now;
|
|
2130
|
+
console.warn(
|
|
2131
|
+
`[FsAgent] refused an incoming tree — re-announcing ${ref.slice(0, 8)}… so the sender can catch up.`
|
|
2132
|
+
);
|
|
2133
|
+
try {
|
|
2134
|
+
await this._sendRef(connector, ref);
|
|
2135
|
+
} catch (err) {
|
|
2136
|
+
console.warn(`[FsAgent] re-announcement failed: ${String(err)}`);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2109
2139
|
_adoptAppliedRef(connector, treeRef) {
|
|
2110
2140
|
if (this._lastAppliedRef && this._lastAppliedRef !== treeRef) {
|
|
2111
2141
|
connector.invalidateSent?.(this._lastAppliedRef);
|
|
@@ -2293,6 +2323,7 @@ ${err.stack}` : String(err);
|
|
|
2293
2323
|
return;
|
|
2294
2324
|
} catch (err) {
|
|
2295
2325
|
if (err instanceof MassDeleteRefusedError) {
|
|
2326
|
+
await this._readvertiseAfterRefusal(connector);
|
|
2296
2327
|
return;
|
|
2297
2328
|
}
|
|
2298
2329
|
if (err instanceof PartialRestoreError) {
|