@peerbit/shared-log 12.3.5 → 13.0.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.
- package/dist/benchmark/sync-batch-sweep.d.ts +2 -0
- package/dist/benchmark/sync-batch-sweep.d.ts.map +1 -0
- package/dist/benchmark/sync-batch-sweep.js +305 -0
- package/dist/benchmark/sync-batch-sweep.js.map +1 -0
- package/dist/src/fanout-envelope.d.ts +18 -0
- package/dist/src/fanout-envelope.d.ts.map +1 -0
- package/dist/src/fanout-envelope.js +85 -0
- package/dist/src/fanout-envelope.js.map +1 -0
- package/dist/src/index.d.ts +55 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1595 -339
- package/dist/src/index.js.map +1 -1
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +21 -5
- package/dist/src/pid.js.map +1 -1
- package/dist/src/ranges.d.ts +3 -1
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +14 -5
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/sync/index.d.ts +45 -1
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.d.ts +13 -2
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +194 -3
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +24 -3
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +330 -32
- package/dist/src/sync/simple.js.map +1 -1
- package/package.json +16 -16
- package/src/fanout-envelope.ts +27 -0
- package/src/index.ts +2162 -691
- package/src/pid.ts +22 -4
- package/src/ranges.ts +14 -4
- package/src/sync/index.ts +53 -1
- package/src/sync/rateless-iblt.ts +237 -4
- package/src/sync/simple.ts +427 -41
package/src/sync/simple.ts
CHANGED
|
@@ -16,7 +16,14 @@ import {
|
|
|
16
16
|
} from "../exchange-heads.js";
|
|
17
17
|
import { TransportMessage } from "../message.js";
|
|
18
18
|
import type { EntryReplicated } from "../ranges.js";
|
|
19
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
RepairSession,
|
|
21
|
+
RepairSessionMode,
|
|
22
|
+
RepairSessionResult,
|
|
23
|
+
SyncOptions,
|
|
24
|
+
SyncableKey,
|
|
25
|
+
Syncronizer,
|
|
26
|
+
} from "./index.js";
|
|
20
27
|
|
|
21
28
|
@variant([0, 1])
|
|
22
29
|
export class RequestMaybeSync extends TransportMessage {
|
|
@@ -95,6 +102,44 @@ const getHashesFromSymbols = async (
|
|
|
95
102
|
return results;
|
|
96
103
|
};
|
|
97
104
|
|
|
105
|
+
const DEFAULT_CONVERGENT_REPAIR_TIMEOUT_MS = 30_000;
|
|
106
|
+
const DEFAULT_CONVERGENT_RETRY_INTERVALS_MS = [0, 1_000, 3_000, 7_000];
|
|
107
|
+
const DEFAULT_BEST_EFFORT_RETRY_INTERVALS_MS = [0];
|
|
108
|
+
const SESSION_POLL_INTERVAL_MS = 100;
|
|
109
|
+
const DEFAULT_MAX_HASHES_PER_MESSAGE = 1_024;
|
|
110
|
+
const DEFAULT_MAX_COORDINATES_PER_MESSAGE = 1_024;
|
|
111
|
+
const DEFAULT_MAX_CONVERGENT_TRACKED_HASHES = 4_096;
|
|
112
|
+
|
|
113
|
+
const createDeferred = <T>() => {
|
|
114
|
+
let resolve!: (value: T | PromiseLike<T>) => void;
|
|
115
|
+
let reject!: (reason?: unknown) => void;
|
|
116
|
+
const promise = new Promise<T>((res, rej) => {
|
|
117
|
+
resolve = res;
|
|
118
|
+
reject = rej;
|
|
119
|
+
});
|
|
120
|
+
return { promise, resolve, reject };
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
type RepairSessionTargetState = {
|
|
124
|
+
unresolved: Set<string>;
|
|
125
|
+
requestedCount: number;
|
|
126
|
+
requestedTotalCount: number;
|
|
127
|
+
attempts: number;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
type RepairSessionState = {
|
|
131
|
+
id: string;
|
|
132
|
+
mode: RepairSessionMode;
|
|
133
|
+
startedAt: number;
|
|
134
|
+
timeoutMs: number;
|
|
135
|
+
retryIntervalsMs: number[];
|
|
136
|
+
targets: Map<string, RepairSessionTargetState>;
|
|
137
|
+
truncated: boolean;
|
|
138
|
+
deferred: ReturnType<typeof createDeferred<RepairSessionResult[]>>;
|
|
139
|
+
cancelled: boolean;
|
|
140
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
141
|
+
};
|
|
142
|
+
|
|
98
143
|
export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
99
144
|
implements Syncronizer<R>
|
|
100
145
|
{
|
|
@@ -110,6 +155,8 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
110
155
|
entryIndex: Index<EntryReplicated<R>, any>;
|
|
111
156
|
coordinateToHash: Cache<string>;
|
|
112
157
|
private syncOptions?: SyncOptions<R>;
|
|
158
|
+
private repairSessionCounter: number;
|
|
159
|
+
private repairSessions: Map<string, RepairSessionState>;
|
|
113
160
|
|
|
114
161
|
// Syncing and dedeplucation work
|
|
115
162
|
syncMoreInterval?: ReturnType<typeof setTimeout>;
|
|
@@ -131,36 +178,354 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
131
178
|
this.entryIndex = properties.entryIndex;
|
|
132
179
|
this.coordinateToHash = properties.coordinateToHash;
|
|
133
180
|
this.syncOptions = properties.sync;
|
|
181
|
+
this.repairSessionCounter = 0;
|
|
182
|
+
this.repairSessions = new Map();
|
|
134
183
|
}
|
|
135
184
|
|
|
136
|
-
|
|
137
|
-
entries: Map<string, EntryReplicated<R
|
|
138
|
-
|
|
139
|
-
}): Promise<void> {
|
|
140
|
-
let hashes: string[];
|
|
185
|
+
private getPrioritizedHashes(
|
|
186
|
+
entries: Map<string, EntryReplicated<R>>,
|
|
187
|
+
): string[] {
|
|
141
188
|
const priorityFn = this.syncOptions?.priority;
|
|
142
|
-
if (priorityFn) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
189
|
+
if (!priorityFn) {
|
|
190
|
+
return [...entries.keys()];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let index = 0;
|
|
194
|
+
const scored: { hash: string; index: number; priority: number }[] = [];
|
|
195
|
+
for (const [hash, entry] of entries) {
|
|
196
|
+
const priorityValue = priorityFn(entry);
|
|
197
|
+
scored.push({
|
|
198
|
+
hash,
|
|
199
|
+
index,
|
|
200
|
+
priority: Number.isFinite(priorityValue) ? priorityValue : 0,
|
|
201
|
+
});
|
|
202
|
+
index += 1;
|
|
203
|
+
}
|
|
204
|
+
scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
|
|
205
|
+
return scored.map((x) => x.hash);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private normalizeRetryIntervals(
|
|
209
|
+
mode: RepairSessionMode,
|
|
210
|
+
retryIntervalsMs?: number[],
|
|
211
|
+
): number[] {
|
|
212
|
+
const defaults =
|
|
213
|
+
mode === "convergent"
|
|
214
|
+
? DEFAULT_CONVERGENT_RETRY_INTERVALS_MS
|
|
215
|
+
: DEFAULT_BEST_EFFORT_RETRY_INTERVALS_MS;
|
|
216
|
+
if (!retryIntervalsMs || retryIntervalsMs.length === 0) {
|
|
217
|
+
return [...defaults];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return [...retryIntervalsMs]
|
|
221
|
+
.map((x) => Math.max(0, Math.floor(x)))
|
|
222
|
+
.filter((x, i, arr) => arr.indexOf(x) === i)
|
|
223
|
+
.sort((a, b) => a - b);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private get maxHashesPerMessage() {
|
|
227
|
+
const value = this.syncOptions?.maxSimpleHashesPerMessage;
|
|
228
|
+
return value && Number.isFinite(value) && value > 0
|
|
229
|
+
? Math.floor(value)
|
|
230
|
+
: DEFAULT_MAX_HASHES_PER_MESSAGE;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private get maxCoordinatesPerMessage() {
|
|
234
|
+
const value = this.syncOptions?.maxSimpleCoordinatesPerMessage;
|
|
235
|
+
return value && Number.isFinite(value) && value > 0
|
|
236
|
+
? Math.floor(value)
|
|
237
|
+
: DEFAULT_MAX_COORDINATES_PER_MESSAGE;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private get maxConvergentTrackedHashes() {
|
|
241
|
+
const value = this.syncOptions?.maxConvergentTrackedHashes;
|
|
242
|
+
return value && Number.isFinite(value) && value > 0
|
|
243
|
+
? Math.floor(value)
|
|
244
|
+
: DEFAULT_MAX_CONVERGENT_TRACKED_HASHES;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private chunk<T>(values: T[], size: number): T[][] {
|
|
248
|
+
if (values.length === 0) {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
const out: T[][] = [];
|
|
252
|
+
for (let i = 0; i < values.length; i += size) {
|
|
253
|
+
out.push(values.slice(i, i + size));
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private isRepairSessionComplete(session: RepairSessionState): boolean {
|
|
259
|
+
for (const state of session.targets.values()) {
|
|
260
|
+
if (state.unresolved.size > 0) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private buildRepairSessionResult(
|
|
268
|
+
session: RepairSessionState,
|
|
269
|
+
completed: boolean,
|
|
270
|
+
): RepairSessionResult[] {
|
|
271
|
+
const durationMs = Date.now() - session.startedAt;
|
|
272
|
+
const out: RepairSessionResult[] = [];
|
|
273
|
+
for (const [target, state] of session.targets) {
|
|
274
|
+
const unresolved = [...state.unresolved];
|
|
275
|
+
out.push({
|
|
276
|
+
target,
|
|
277
|
+
requested: state.requestedCount,
|
|
278
|
+
resolved: state.requestedCount - unresolved.length,
|
|
279
|
+
unresolved,
|
|
280
|
+
attempts: state.attempts,
|
|
281
|
+
durationMs,
|
|
282
|
+
completed,
|
|
283
|
+
requestedTotal: state.requestedTotalCount,
|
|
284
|
+
truncated: session.truncated,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private finalizeRepairSession(sessionId: string, completed: boolean): void {
|
|
291
|
+
const session = this.repairSessions.get(sessionId);
|
|
292
|
+
if (!session) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
this.repairSessions.delete(sessionId);
|
|
296
|
+
session.cancelled = true;
|
|
297
|
+
if (session.timer) {
|
|
298
|
+
clearTimeout(session.timer);
|
|
299
|
+
}
|
|
300
|
+
session.deferred.resolve(this.buildRepairSessionResult(session, completed));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private async refreshRepairSessionState(sessionId: string): Promise<void> {
|
|
304
|
+
const session = this.repairSessions.get(sessionId);
|
|
305
|
+
if (!session) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
for (const state of session.targets.values()) {
|
|
309
|
+
for (const hash of [...state.unresolved]) {
|
|
310
|
+
if (await this.log.has(hash)) {
|
|
311
|
+
state.unresolved.delete(hash);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private markRepairSessionResolvedHashes(hashes: string[]): void {
|
|
318
|
+
if (hashes.length === 0 || this.repairSessions.size === 0) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
for (const [sessionId, session] of this.repairSessions) {
|
|
322
|
+
for (const state of session.targets.values()) {
|
|
323
|
+
for (const hash of hashes) {
|
|
324
|
+
state.unresolved.delete(hash);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (this.isRepairSessionComplete(session)) {
|
|
328
|
+
this.finalizeRepairSession(sessionId, true);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private async runRepairSession(sessionId: string): Promise<void> {
|
|
334
|
+
const session = this.repairSessions.get(sessionId);
|
|
335
|
+
if (!session) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let previousDelay = 0;
|
|
340
|
+
for (const delayMs of session.retryIntervalsMs) {
|
|
341
|
+
if (!this.repairSessions.has(sessionId) || this.closed) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const waitMs = Math.max(0, delayMs - previousDelay);
|
|
346
|
+
previousDelay = delayMs;
|
|
347
|
+
if (waitMs > 0) {
|
|
348
|
+
await new Promise<void>((resolve) => {
|
|
349
|
+
const timer = setTimeout(resolve, waitMs);
|
|
350
|
+
timer.unref?.();
|
|
151
351
|
});
|
|
152
|
-
index += 1;
|
|
153
352
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
353
|
+
if (!this.repairSessions.has(sessionId) || this.closed) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
await this.refreshRepairSessionState(sessionId);
|
|
358
|
+
const current = this.repairSessions.get(sessionId);
|
|
359
|
+
if (!current) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (this.isRepairSessionComplete(current)) {
|
|
363
|
+
this.finalizeRepairSession(sessionId, true);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (const [target, state] of current.targets) {
|
|
368
|
+
if (state.unresolved.size === 0) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
state.attempts += 1;
|
|
372
|
+
try {
|
|
373
|
+
await this.requestSync([...state.unresolved], [target]);
|
|
374
|
+
} catch {
|
|
375
|
+
// Best-effort: keep unresolved and let retries/timeout determine outcome.
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
await this.refreshRepairSessionState(sessionId);
|
|
380
|
+
const afterSend = this.repairSessions.get(sessionId);
|
|
381
|
+
if (!afterSend) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (this.isRepairSessionComplete(afterSend)) {
|
|
385
|
+
this.finalizeRepairSession(sessionId, true);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (afterSend.mode === "best-effort") {
|
|
390
|
+
this.finalizeRepairSession(sessionId, false);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (;;) {
|
|
396
|
+
if (!this.repairSessions.has(sessionId) || this.closed) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
await this.refreshRepairSessionState(sessionId);
|
|
400
|
+
const current = this.repairSessions.get(sessionId);
|
|
401
|
+
if (!current) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (this.isRepairSessionComplete(current)) {
|
|
405
|
+
this.finalizeRepairSession(sessionId, true);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
await new Promise<void>((resolve) => {
|
|
409
|
+
const timer = setTimeout(resolve, SESSION_POLL_INTERVAL_MS);
|
|
410
|
+
timer.unref?.();
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
startRepairSession(properties: {
|
|
416
|
+
entries: Map<string, EntryReplicated<R>>;
|
|
417
|
+
targets: string[];
|
|
418
|
+
mode?: RepairSessionMode;
|
|
419
|
+
timeoutMs?: number;
|
|
420
|
+
retryIntervalsMs?: number[];
|
|
421
|
+
}): RepairSession {
|
|
422
|
+
const mode = properties.mode ?? "best-effort";
|
|
423
|
+
const startedAt = Date.now();
|
|
424
|
+
const timeoutMs = Math.max(
|
|
425
|
+
1,
|
|
426
|
+
Math.floor(
|
|
427
|
+
properties.timeoutMs ??
|
|
428
|
+
(mode === "convergent"
|
|
429
|
+
? DEFAULT_CONVERGENT_REPAIR_TIMEOUT_MS
|
|
430
|
+
: DEFAULT_CONVERGENT_REPAIR_TIMEOUT_MS),
|
|
431
|
+
),
|
|
432
|
+
);
|
|
433
|
+
const retryIntervalsMs = this.normalizeRetryIntervals(
|
|
434
|
+
mode,
|
|
435
|
+
properties.retryIntervalsMs,
|
|
436
|
+
);
|
|
437
|
+
const allHashes = this.getPrioritizedHashes(properties.entries);
|
|
438
|
+
const trackedHashes =
|
|
439
|
+
mode === "convergent" && allHashes.length > this.maxConvergentTrackedHashes
|
|
440
|
+
? allHashes.slice(0, this.maxConvergentTrackedHashes)
|
|
441
|
+
: allHashes;
|
|
442
|
+
const truncated = trackedHashes.length < allHashes.length;
|
|
443
|
+
const targets = [...new Set(properties.targets)];
|
|
444
|
+
const id = `repair-${++this.repairSessionCounter}`;
|
|
445
|
+
const deferred = createDeferred<RepairSessionResult[]>();
|
|
446
|
+
|
|
447
|
+
const targetStates = new Map<string, RepairSessionTargetState>();
|
|
448
|
+
for (const target of targets) {
|
|
449
|
+
targetStates.set(target, {
|
|
450
|
+
unresolved: new Set(trackedHashes),
|
|
451
|
+
requestedCount: trackedHashes.length,
|
|
452
|
+
requestedTotalCount: allHashes.length,
|
|
453
|
+
attempts: 0,
|
|
454
|
+
});
|
|
158
455
|
}
|
|
159
456
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
mode
|
|
457
|
+
const session: RepairSessionState = {
|
|
458
|
+
id,
|
|
459
|
+
mode,
|
|
460
|
+
startedAt,
|
|
461
|
+
timeoutMs,
|
|
462
|
+
retryIntervalsMs,
|
|
463
|
+
targets: targetStates,
|
|
464
|
+
truncated,
|
|
465
|
+
deferred,
|
|
466
|
+
cancelled: false,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
if (allHashes.length === 0 || targets.length === 0) {
|
|
470
|
+
deferred.resolve(this.buildRepairSessionResult(session, true));
|
|
471
|
+
return {
|
|
472
|
+
id,
|
|
473
|
+
done: deferred.promise,
|
|
474
|
+
cancel: () => {
|
|
475
|
+
// no-op
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// For capped convergent sessions, still dispatch the full set once so large
|
|
481
|
+
// repairs are not limited to tracked hashes.
|
|
482
|
+
if (mode === "convergent" && truncated) {
|
|
483
|
+
void this.onMaybeMissingEntries({
|
|
484
|
+
entries: properties.entries,
|
|
485
|
+
targets,
|
|
486
|
+
}).catch(() => {
|
|
487
|
+
// Best-effort: retries on tracked hashes continue via runRepairSession.
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
session.timer = setTimeout(() => {
|
|
492
|
+
this.finalizeRepairSession(id, false);
|
|
493
|
+
}, timeoutMs);
|
|
494
|
+
session.timer.unref?.();
|
|
495
|
+
|
|
496
|
+
this.repairSessions.set(id, session);
|
|
497
|
+
void this.runRepairSession(id).catch(() => {
|
|
498
|
+
this.finalizeRepairSession(id, false);
|
|
163
499
|
});
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
id,
|
|
503
|
+
done: deferred.promise,
|
|
504
|
+
cancel: () => {
|
|
505
|
+
this.finalizeRepairSession(id, false);
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
onMaybeMissingEntries(properties: {
|
|
511
|
+
entries: Map<string, EntryReplicated<R>>;
|
|
512
|
+
targets: string[];
|
|
513
|
+
}): Promise<void> {
|
|
514
|
+
const hashes = this.getPrioritizedHashes(properties.entries);
|
|
515
|
+
const chunks = this.chunk(hashes, this.maxHashesPerMessage);
|
|
516
|
+
return chunks.reduce(
|
|
517
|
+
(promise, chunk) =>
|
|
518
|
+
promise.then(() =>
|
|
519
|
+
this.rpc.send(new RequestMaybeSync({ hashes: chunk }), {
|
|
520
|
+
priority: 1,
|
|
521
|
+
mode: new SilentDelivery({
|
|
522
|
+
to: properties.targets,
|
|
523
|
+
redundancy: 1,
|
|
524
|
+
}),
|
|
525
|
+
}),
|
|
526
|
+
),
|
|
527
|
+
Promise.resolve(),
|
|
528
|
+
);
|
|
164
529
|
}
|
|
165
530
|
|
|
166
531
|
async onMessage(
|
|
@@ -210,7 +575,9 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
210
575
|
entries: EntryWithRefs<any>[];
|
|
211
576
|
from: PublicSignKey;
|
|
212
577
|
}): Promise<void> | void {
|
|
578
|
+
const resolvedHashes: string[] = [];
|
|
213
579
|
for (const entry of properties.entries) {
|
|
580
|
+
resolvedHashes.push(entry.entry.hash);
|
|
214
581
|
const set = this.syncInFlight.get(properties.from.hashcode());
|
|
215
582
|
if (set) {
|
|
216
583
|
set.delete(entry.entry.hash);
|
|
@@ -219,6 +586,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
219
586
|
}
|
|
220
587
|
}
|
|
221
588
|
}
|
|
589
|
+
this.markRepairSessionResolvedHashes(resolvedHashes);
|
|
222
590
|
}
|
|
223
591
|
|
|
224
592
|
async queueSync(
|
|
@@ -276,16 +644,29 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
276
644
|
}
|
|
277
645
|
|
|
278
646
|
const isBigInt = typeof hashes[0] === "bigint";
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
{
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
647
|
+
if (isBigInt) {
|
|
648
|
+
const chunks = this.chunk(
|
|
649
|
+
hashes as bigint[],
|
|
650
|
+
this.maxCoordinatesPerMessage,
|
|
651
|
+
);
|
|
652
|
+
for (const chunk of chunks) {
|
|
653
|
+
await this.rpc.send(
|
|
654
|
+
new RequestMaybeSyncCoordinate({ hashNumbers: chunk }),
|
|
655
|
+
{
|
|
656
|
+
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
657
|
+
priority: 1,
|
|
658
|
+
},
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
const chunks = this.chunk(hashes as string[], this.maxHashesPerMessage);
|
|
663
|
+
for (const chunk of chunks) {
|
|
664
|
+
await this.rpc.send(new ResponseMaybeSync({ hashes: chunk }), {
|
|
665
|
+
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
666
|
+
priority: 1,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
289
670
|
}
|
|
290
671
|
private async checkHasCoordinateOrHash(key: string | bigint) {
|
|
291
672
|
return typeof key === "bigint"
|
|
@@ -366,10 +747,14 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
366
747
|
this.syncInFlightQueue.clear();
|
|
367
748
|
this.syncInFlightQueueInverted.clear();
|
|
368
749
|
this.syncInFlight.clear();
|
|
750
|
+
for (const sessionId of [...this.repairSessions.keys()]) {
|
|
751
|
+
this.finalizeRepairSession(sessionId, false);
|
|
752
|
+
}
|
|
369
753
|
clearTimeout(this.syncMoreInterval);
|
|
370
754
|
}
|
|
371
755
|
onEntryAdded(entry: Entry<any>): void {
|
|
372
|
-
|
|
756
|
+
this.clearSyncProcess(entry.hash);
|
|
757
|
+
this.markRepairSessionResolvedHashes([entry.hash]);
|
|
373
758
|
}
|
|
374
759
|
|
|
375
760
|
onEntryRemoved(hash: string): void {
|
|
@@ -393,17 +778,18 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
393
778
|
}
|
|
394
779
|
}
|
|
395
780
|
|
|
396
|
-
onPeerDisconnected(key: PublicSignKey): Promise<void> | void {
|
|
397
|
-
|
|
781
|
+
onPeerDisconnected(key: PublicSignKey | string): Promise<void> | void {
|
|
782
|
+
const publicKeyHash = typeof key === "string" ? key : key.hashcode();
|
|
783
|
+
return this.clearSyncProcessPublicKeyHash(publicKeyHash);
|
|
398
784
|
}
|
|
399
|
-
private
|
|
400
|
-
this.syncInFlight.delete(
|
|
401
|
-
const map = this.syncInFlightQueueInverted.get(
|
|
785
|
+
private clearSyncProcessPublicKeyHash(publicKeyHash: string) {
|
|
786
|
+
this.syncInFlight.delete(publicKeyHash);
|
|
787
|
+
const map = this.syncInFlightQueueInverted.get(publicKeyHash);
|
|
402
788
|
if (map) {
|
|
403
789
|
for (const hash of map) {
|
|
404
790
|
const arr = this.syncInFlightQueue.get(hash);
|
|
405
791
|
if (arr) {
|
|
406
|
-
const filtered = arr.filter((x) =>
|
|
792
|
+
const filtered = arr.filter((x) => x.hashcode() !== publicKeyHash);
|
|
407
793
|
if (filtered.length > 0) {
|
|
408
794
|
this.syncInFlightQueue.set(hash, filtered);
|
|
409
795
|
} else {
|
|
@@ -411,7 +797,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
411
797
|
}
|
|
412
798
|
}
|
|
413
799
|
}
|
|
414
|
-
this.syncInFlightQueueInverted.delete(
|
|
800
|
+
this.syncInFlightQueueInverted.delete(publicKeyHash);
|
|
415
801
|
}
|
|
416
802
|
}
|
|
417
803
|
|