@peerbit/shared-log 13.2.12 → 13.2.14
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/receive-prune.js +31 -29
- package/dist/benchmark/receive-prune.js.map +1 -1
- package/dist/src/checked-prune.d.ts +43 -10
- package/dist/src/checked-prune.d.ts.map +1 -1
- package/dist/src/checked-prune.js +257 -27
- package/dist/src/checked-prune.js.map +1 -1
- package/dist/src/exchange-heads.d.ts +26 -1
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +114 -36
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts +71 -14
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3492 -1493
- package/dist/src/index.js.map +1 -1
- package/dist/src/sync/index.d.ts +2 -0
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.d.ts +96 -20
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +1616 -428
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +176 -1
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +2919 -525
- package/dist/src/sync/simple.js.map +1 -1
- package/package.json +11 -11
- package/src/checked-prune.ts +338 -29
- package/src/exchange-heads.ts +74 -37
- package/src/index.ts +6370 -2792
- package/src/sync/index.ts +2 -1
- package/src/sync/rateless-iblt.ts +2036 -498
- package/src/sync/simple.ts +3779 -634
package/src/checked-prune.ts
CHANGED
|
@@ -10,10 +10,18 @@ export type CheckedPruneEntry<T, R extends "u32" | "u64"> =
|
|
|
10
10
|
| EntryReplicated<R>;
|
|
11
11
|
|
|
12
12
|
export type CheckedPrunePendingDelete = {
|
|
13
|
+
requestId: Uint8Array;
|
|
14
|
+
retryOnInvalidation?: boolean;
|
|
13
15
|
promise: DeferredPromise<void>;
|
|
14
16
|
clear: () => void;
|
|
15
|
-
resolve: (
|
|
16
|
-
|
|
17
|
+
resolve: (
|
|
18
|
+
publicKeyHash: string,
|
|
19
|
+
requestId: Uint8Array,
|
|
20
|
+
) => Promise<void> | void;
|
|
21
|
+
reject(
|
|
22
|
+
reason: any,
|
|
23
|
+
options?: { preserveRetry?: boolean },
|
|
24
|
+
): Promise<void> | void;
|
|
17
25
|
};
|
|
18
26
|
|
|
19
27
|
export type CheckedPruneRetryState<T, R extends "u32" | "u64"> = {
|
|
@@ -23,6 +31,18 @@ export type CheckedPruneRetryState<T, R extends "u32" | "u64"> = {
|
|
|
23
31
|
leaders: CheckedPruneLeaderMap | Set<string>;
|
|
24
32
|
};
|
|
25
33
|
|
|
34
|
+
export type CheckedPruneInvalidatedGeneration<T, R extends "u32" | "u64"> = {
|
|
35
|
+
hash: string;
|
|
36
|
+
pending: CheckedPrunePendingDelete;
|
|
37
|
+
entry: CheckedPruneEntry<T, R>;
|
|
38
|
+
leaders: CheckedPruneLeaderMap | Set<string>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type CheckedPruneRestartCandidate<T, R extends "u32" | "u64"> = Omit<
|
|
42
|
+
CheckedPruneInvalidatedGeneration<T, R>,
|
|
43
|
+
"pending"
|
|
44
|
+
>;
|
|
45
|
+
|
|
26
46
|
type CheckedPruneSessionPhase =
|
|
27
47
|
| "candidate"
|
|
28
48
|
| "requested"
|
|
@@ -40,6 +60,7 @@ type CheckedPruneSession<T, R extends "u32" | "u64"> = {
|
|
|
40
60
|
retry?: CheckedPruneRetryState<T, R>;
|
|
41
61
|
contacted: Set<string>;
|
|
42
62
|
confirmed: Set<string>;
|
|
63
|
+
revoked: Set<string>;
|
|
43
64
|
};
|
|
44
65
|
|
|
45
66
|
export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
@@ -48,6 +69,10 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
48
69
|
readonly responseReplicatorSet = new Map<string, Set<string>>();
|
|
49
70
|
readonly retries = new Map<string, CheckedPruneRetryState<T, R>>();
|
|
50
71
|
private readonly sessions = new Map<string, CheckedPruneSession<T, R>>();
|
|
72
|
+
private readonly grantSends = new Map<string, Set<Promise<void>>>();
|
|
73
|
+
private readonly peerRemovalFences = new Map<string, number>();
|
|
74
|
+
private readonly restartReservations = new Map<string, object>();
|
|
75
|
+
private readonly candidateTokens = new Map<string, object>();
|
|
51
76
|
|
|
52
77
|
private getOrCreateSession(hash: string): CheckedPruneSession<T, R> {
|
|
53
78
|
let session = this.sessions.get(hash);
|
|
@@ -56,6 +81,7 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
56
81
|
phase: "candidate",
|
|
57
82
|
contacted: new Set(),
|
|
58
83
|
confirmed: new Set(),
|
|
84
|
+
revoked: new Set(),
|
|
59
85
|
};
|
|
60
86
|
this.sessions.set(hash, session);
|
|
61
87
|
}
|
|
@@ -82,7 +108,8 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
82
108
|
this.pendingDeletes.has(hash) ||
|
|
83
109
|
this.requestIPruneSent.has(hash) ||
|
|
84
110
|
this.responseReplicatorSet.has(hash) ||
|
|
85
|
-
this.retries.has(hash)
|
|
111
|
+
this.retries.has(hash) ||
|
|
112
|
+
this.candidateTokens.has(hash)
|
|
86
113
|
) {
|
|
87
114
|
return;
|
|
88
115
|
}
|
|
@@ -94,10 +121,27 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
94
121
|
entry: CheckedPruneEntry<T, R>,
|
|
95
122
|
leaders: CheckedPruneLeaderMap | Set<string>,
|
|
96
123
|
) {
|
|
124
|
+
this.restartReservations.delete(hash);
|
|
97
125
|
const session = this.remember(hash, entry, leaders);
|
|
98
126
|
if (!this.hasActiveWork(hash)) {
|
|
99
127
|
session.phase = "candidate";
|
|
100
128
|
}
|
|
129
|
+
const token = {};
|
|
130
|
+
this.candidateTokens.set(hash, token);
|
|
131
|
+
return token;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
isCandidateTokenCurrent(hash: string, token: object) {
|
|
135
|
+
return this.candidateTokens.get(hash) === token;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
hasCandidate(hash: string) {
|
|
139
|
+
return this.candidateTokens.has(hash);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
invalidateCandidateToken(hash: string) {
|
|
143
|
+
this.candidateTokens.delete(hash);
|
|
144
|
+
this.deleteSessionIfIdle(hash);
|
|
101
145
|
}
|
|
102
146
|
|
|
103
147
|
hasActiveWork(hash: string) {
|
|
@@ -105,7 +149,8 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
105
149
|
this.pendingDeletes.has(hash) ||
|
|
106
150
|
this.requestIPruneSent.has(hash) ||
|
|
107
151
|
this.responseReplicatorSet.has(hash) ||
|
|
108
|
-
this.retries.has(hash)
|
|
152
|
+
this.retries.has(hash) ||
|
|
153
|
+
this.candidateTokens.has(hash)
|
|
109
154
|
);
|
|
110
155
|
}
|
|
111
156
|
|
|
@@ -117,15 +162,39 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
117
162
|
return this.pendingDeletes.has(hash);
|
|
118
163
|
}
|
|
119
164
|
|
|
165
|
+
getRestartCandidate(
|
|
166
|
+
hash: string,
|
|
167
|
+
): CheckedPruneRestartCandidate<T, R> | undefined {
|
|
168
|
+
const session = this.sessions.get(hash);
|
|
169
|
+
if (!session?.entry || !session.leaders) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
hash,
|
|
174
|
+
entry: session.entry,
|
|
175
|
+
leaders:
|
|
176
|
+
session.leaders instanceof Map
|
|
177
|
+
? new Map(session.leaders)
|
|
178
|
+
: new Set(session.leaders),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
120
182
|
setPendingDelete(
|
|
121
183
|
hash: string,
|
|
122
184
|
pending: CheckedPrunePendingDelete,
|
|
123
185
|
entry: CheckedPruneEntry<T, R>,
|
|
124
186
|
leaders: CheckedPruneLeaderMap | Set<string>,
|
|
125
187
|
) {
|
|
188
|
+
this.restartReservations.delete(hash);
|
|
189
|
+
this.candidateTokens.delete(hash);
|
|
190
|
+
this.requestIPruneSent.delete(hash);
|
|
191
|
+
this.responseReplicatorSet.delete(hash);
|
|
126
192
|
this.pendingDeletes.set(hash, pending);
|
|
127
193
|
const session = this.remember(hash, entry, leaders);
|
|
128
194
|
session.pending = pending;
|
|
195
|
+
session.contacted.clear();
|
|
196
|
+
session.confirmed.clear();
|
|
197
|
+
session.revoked.clear();
|
|
129
198
|
session.phase = "requested";
|
|
130
199
|
}
|
|
131
200
|
|
|
@@ -133,9 +202,14 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
133
202
|
const current = this.pendingDeletes.get(hash);
|
|
134
203
|
if (!pending || current === pending) {
|
|
135
204
|
this.pendingDeletes.delete(hash);
|
|
205
|
+
this.requestIPruneSent.delete(hash);
|
|
206
|
+
this.responseReplicatorSet.delete(hash);
|
|
136
207
|
const session = this.sessions.get(hash);
|
|
137
208
|
if (session && session.pending === current) {
|
|
138
209
|
session.pending = undefined;
|
|
210
|
+
session.contacted.clear();
|
|
211
|
+
session.confirmed.clear();
|
|
212
|
+
session.revoked.clear();
|
|
139
213
|
}
|
|
140
214
|
this.deleteSessionIfIdle(hash);
|
|
141
215
|
}
|
|
@@ -150,6 +224,8 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
150
224
|
}
|
|
151
225
|
|
|
152
226
|
setRetry(hash: string, state: CheckedPruneRetryState<T, R>) {
|
|
227
|
+
this.restartReservations.delete(hash);
|
|
228
|
+
this.candidateTokens.delete(hash);
|
|
153
229
|
this.retries.set(hash, state);
|
|
154
230
|
const session = this.remember(hash, state.entry, state.leaders);
|
|
155
231
|
session.retry = state;
|
|
@@ -157,6 +233,8 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
157
233
|
}
|
|
158
234
|
|
|
159
235
|
clearRetry(hash: string) {
|
|
236
|
+
this.restartReservations.delete(hash);
|
|
237
|
+
this.candidateTokens.delete(hash);
|
|
160
238
|
const state = this.retries.get(hash);
|
|
161
239
|
if (state?.timer) {
|
|
162
240
|
clearTimeout(state.timer);
|
|
@@ -178,34 +256,78 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
178
256
|
return state;
|
|
179
257
|
}
|
|
180
258
|
|
|
181
|
-
|
|
259
|
+
isCurrentRequest(hash: string, pending: CheckedPrunePendingDelete) {
|
|
260
|
+
return this.pendingDeletes.get(hash) === pending;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
isCurrentRequestId(
|
|
264
|
+
hash: string,
|
|
265
|
+
pending: CheckedPrunePendingDelete,
|
|
266
|
+
requestId: Uint8Array,
|
|
267
|
+
) {
|
|
268
|
+
if (!this.isCurrentRequest(hash, pending)) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (pending.requestId.byteLength !== requestId.byteLength) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
return pending.requestId.every(
|
|
275
|
+
(value, index) => value === requestId[index],
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
addRequestSent(
|
|
280
|
+
hash: string,
|
|
281
|
+
peer: string,
|
|
282
|
+
pending: CheckedPrunePendingDelete,
|
|
283
|
+
) {
|
|
284
|
+
if (!this.isCurrentRequest(hash, pending)) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const session = this.getOrCreateSession(hash);
|
|
288
|
+
if (session.revoked.has(peer)) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
182
291
|
let set = this.requestIPruneSent.get(hash);
|
|
183
292
|
if (!set) {
|
|
184
293
|
set = new Set();
|
|
185
294
|
this.requestIPruneSent.set(hash, set);
|
|
186
295
|
}
|
|
187
296
|
set.add(peer);
|
|
188
|
-
const session = this.getOrCreateSession(hash);
|
|
189
297
|
session.contacted.add(peer);
|
|
190
298
|
if (session.phase === "candidate") {
|
|
191
299
|
session.phase = "requested";
|
|
192
300
|
}
|
|
301
|
+
return true;
|
|
193
302
|
}
|
|
194
303
|
|
|
195
304
|
removeRequestSent(hash: string, peer?: string) {
|
|
196
305
|
if (!peer) {
|
|
197
|
-
this.requestIPruneSent.delete(hash);
|
|
198
306
|
const session = this.sessions.get(hash);
|
|
307
|
+
if (session?.pending) {
|
|
308
|
+
for (const contactedPeer of session.contacted) {
|
|
309
|
+
session.revoked.add(contactedPeer);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
this.requestIPruneSent.delete(hash);
|
|
199
313
|
session?.contacted.clear();
|
|
314
|
+
this.clearConfirmedReplicators(hash);
|
|
200
315
|
this.deleteSessionIfIdle(hash);
|
|
201
316
|
return;
|
|
202
317
|
}
|
|
318
|
+
const session = this.sessions.get(hash);
|
|
319
|
+
if (session?.pending) {
|
|
320
|
+
// A contact removed during this pending generation must never become
|
|
321
|
+
// authorized again by a delayed receipt carrying the old generation ID.
|
|
322
|
+
session.revoked.add(peer);
|
|
323
|
+
}
|
|
203
324
|
const set = this.requestIPruneSent.get(hash);
|
|
204
325
|
if (!set) {
|
|
326
|
+
this.removeConfirmedReplicator(hash, peer);
|
|
205
327
|
return;
|
|
206
328
|
}
|
|
207
329
|
set.delete(peer);
|
|
208
|
-
|
|
330
|
+
this.removeConfirmedReplicator(hash, peer);
|
|
209
331
|
session?.contacted.delete(peer);
|
|
210
332
|
if (set.size === 0) {
|
|
211
333
|
this.requestIPruneSent.delete(hash);
|
|
@@ -214,15 +336,23 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
214
336
|
}
|
|
215
337
|
|
|
216
338
|
removeRequestsSent(hashes: Iterable<string>, peer?: string) {
|
|
217
|
-
if (this.requestIPruneSent.size === 0) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
339
|
for (const hash of hashes) {
|
|
221
340
|
this.removeRequestSent(hash, peer);
|
|
222
341
|
}
|
|
223
342
|
}
|
|
224
343
|
|
|
225
|
-
addConfirmedReplicator(
|
|
344
|
+
addConfirmedReplicator(
|
|
345
|
+
hash: string,
|
|
346
|
+
peer: string,
|
|
347
|
+
pending: CheckedPrunePendingDelete,
|
|
348
|
+
requestId: Uint8Array,
|
|
349
|
+
) {
|
|
350
|
+
if (
|
|
351
|
+
!this.isCurrentRequestId(hash, pending, requestId) ||
|
|
352
|
+
!this.requestIPruneSent.get(hash)?.has(peer)
|
|
353
|
+
) {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
226
356
|
let set = this.responseReplicatorSet.get(hash);
|
|
227
357
|
if (!set) {
|
|
228
358
|
set = new Set();
|
|
@@ -282,24 +412,178 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
282
412
|
return this.requestIPruneSent.get(hash);
|
|
283
413
|
}
|
|
284
414
|
|
|
285
|
-
|
|
415
|
+
hasRevokedLeader(
|
|
416
|
+
hash: string,
|
|
417
|
+
pending: CheckedPrunePendingDelete,
|
|
418
|
+
leaders: CheckedPruneLeaderMap | Set<string>,
|
|
419
|
+
) {
|
|
420
|
+
if (!this.isCurrentRequest(hash, pending)) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
const revoked = this.sessions.get(hash)?.revoked;
|
|
424
|
+
if (!revoked || revoked.size === 0) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
for (const leader of leaders.keys()) {
|
|
428
|
+
if (revoked.has(leader)) {
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
trackGrantSend(hashes: Iterable<string>, send: Promise<void>) {
|
|
436
|
+
const uniqueHashes = [...new Set(hashes)];
|
|
437
|
+
const observed = send.then(
|
|
438
|
+
() => undefined,
|
|
439
|
+
() => undefined,
|
|
440
|
+
);
|
|
441
|
+
for (const hash of uniqueHashes) {
|
|
442
|
+
let sends = this.grantSends.get(hash);
|
|
443
|
+
if (!sends) {
|
|
444
|
+
sends = new Set();
|
|
445
|
+
this.grantSends.set(hash, sends);
|
|
446
|
+
}
|
|
447
|
+
sends.add(observed);
|
|
448
|
+
}
|
|
449
|
+
void observed.finally(() => {
|
|
450
|
+
for (const hash of uniqueHashes) {
|
|
451
|
+
const sends = this.grantSends.get(hash);
|
|
452
|
+
sends?.delete(observed);
|
|
453
|
+
if (sends?.size === 0) {
|
|
454
|
+
this.grantSends.delete(hash);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
return observed;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
waitForGrantSends(hash: string): Promise<void> | undefined {
|
|
462
|
+
const sends = this.grantSends.get(hash);
|
|
463
|
+
if (!sends || sends.size === 0) {
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
return Promise.all(sends).then(() => undefined);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
reserveRestart(hash: string) {
|
|
470
|
+
const reservation = {};
|
|
471
|
+
this.restartReservations.set(hash, reservation);
|
|
472
|
+
return reservation;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
consumeRestartReservation(hash: string, reservation: object) {
|
|
476
|
+
if (this.restartReservations.get(hash) !== reservation) {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
this.restartReservations.delete(hash);
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
cancelRestartReservation(hash: string, reservation: object) {
|
|
484
|
+
if (this.restartReservations.get(hash) === reservation) {
|
|
485
|
+
this.restartReservations.delete(hash);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
hasRestartReservation(hash: string) {
|
|
490
|
+
return this.restartReservations.has(hash);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
getExactConfirmedReplicators(
|
|
494
|
+
hash: string,
|
|
495
|
+
pending: CheckedPrunePendingDelete,
|
|
496
|
+
) {
|
|
497
|
+
const exact = new Set<string>();
|
|
498
|
+
if (!this.isCurrentRequest(hash, pending)) {
|
|
499
|
+
return exact;
|
|
500
|
+
}
|
|
501
|
+
const contacted = this.requestIPruneSent.get(hash);
|
|
502
|
+
const confirmed = this.responseReplicatorSet.get(hash);
|
|
503
|
+
const revoked = this.sessions.get(hash)?.revoked;
|
|
504
|
+
if (!contacted || !confirmed) {
|
|
505
|
+
return exact;
|
|
506
|
+
}
|
|
507
|
+
for (const peer of confirmed) {
|
|
508
|
+
if (
|
|
509
|
+
contacted.has(peer) &&
|
|
510
|
+
!revoked?.has(peer) &&
|
|
511
|
+
!this.isPeerRemovalFenced(peer)
|
|
512
|
+
) {
|
|
513
|
+
exact.add(peer);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return exact;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
fencePeerRemoval(peer: string) {
|
|
520
|
+
this.peerRemovalFences.set(
|
|
521
|
+
peer,
|
|
522
|
+
(this.peerRemovalFences.get(peer) ?? 0) + 1,
|
|
523
|
+
);
|
|
524
|
+
let released = false;
|
|
525
|
+
return () => {
|
|
526
|
+
if (released) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
released = true;
|
|
530
|
+
const remaining = (this.peerRemovalFences.get(peer) ?? 1) - 1;
|
|
531
|
+
if (remaining > 0) {
|
|
532
|
+
this.peerRemovalFences.set(peer, remaining);
|
|
533
|
+
} else {
|
|
534
|
+
this.peerRemovalFences.delete(peer);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
isPeerRemovalFenced(peer: string) {
|
|
540
|
+
return (this.peerRemovalFences.get(peer) ?? 0) > 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
markRemoving(hash: string, pending?: CheckedPrunePendingDelete) {
|
|
544
|
+
if (pending && !this.isCurrentRequest(hash, pending)) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
286
547
|
const session = this.getOrCreateSession(hash);
|
|
287
548
|
session.phase = "removing";
|
|
549
|
+
return true;
|
|
288
550
|
}
|
|
289
551
|
|
|
290
|
-
markDone(hash: string) {
|
|
552
|
+
markDone(hash: string, pending?: CheckedPrunePendingDelete) {
|
|
553
|
+
if (pending && !this.isCurrentRequest(hash, pending)) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
291
556
|
const session = this.sessions.get(hash);
|
|
292
557
|
if (session) {
|
|
293
558
|
session.phase = "done";
|
|
294
559
|
}
|
|
560
|
+
this.restartReservations.delete(hash);
|
|
561
|
+
this.candidateTokens.delete(hash);
|
|
295
562
|
this.pendingDeletes.delete(hash);
|
|
296
563
|
this.requestIPruneSent.delete(hash);
|
|
297
564
|
this.responseReplicatorSet.delete(hash);
|
|
298
565
|
this.clearRetry(hash);
|
|
299
566
|
this.sessions.delete(hash);
|
|
567
|
+
return true;
|
|
300
568
|
}
|
|
301
569
|
|
|
302
|
-
markCancelled(
|
|
570
|
+
markCancelled(
|
|
571
|
+
hash: string,
|
|
572
|
+
pendingOrOptions?: CheckedPrunePendingDelete | { preserveRetry?: boolean },
|
|
573
|
+
options?: { preserveRetry?: boolean },
|
|
574
|
+
) {
|
|
575
|
+
const pending =
|
|
576
|
+
pendingOrOptions && "promise" in pendingOrOptions
|
|
577
|
+
? pendingOrOptions
|
|
578
|
+
: undefined;
|
|
579
|
+
const resolvedOptions: { preserveRetry?: boolean } | undefined = pending
|
|
580
|
+
? options
|
|
581
|
+
: (pendingOrOptions as { preserveRetry?: boolean } | undefined);
|
|
582
|
+
if (pending && !this.isCurrentRequest(hash, pending)) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
this.restartReservations.delete(hash);
|
|
586
|
+
this.candidateTokens.delete(hash);
|
|
303
587
|
const retry = this.retries.get(hash);
|
|
304
588
|
const session = this.sessions.get(hash);
|
|
305
589
|
if (session) {
|
|
@@ -307,36 +591,57 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
307
591
|
session.pending = undefined;
|
|
308
592
|
session.contacted.clear();
|
|
309
593
|
session.confirmed.clear();
|
|
310
|
-
|
|
594
|
+
session.revoked.clear();
|
|
595
|
+
if (!resolvedOptions?.preserveRetry || session.retry !== retry) {
|
|
311
596
|
session.retry = undefined;
|
|
312
597
|
}
|
|
313
598
|
}
|
|
599
|
+
if (!pending || this.pendingDeletes.get(hash) === pending) {
|
|
600
|
+
this.pendingDeletes.delete(hash);
|
|
601
|
+
}
|
|
314
602
|
this.requestIPruneSent.delete(hash);
|
|
315
603
|
this.responseReplicatorSet.delete(hash);
|
|
316
|
-
if (!
|
|
604
|
+
if (!resolvedOptions?.preserveRetry) {
|
|
317
605
|
this.clearRetry(hash);
|
|
318
606
|
}
|
|
319
607
|
this.deleteSessionIfIdle(hash);
|
|
608
|
+
return true;
|
|
320
609
|
}
|
|
321
610
|
|
|
322
611
|
cleanupPeer(peer: string) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
612
|
+
const invalidated: CheckedPruneInvalidatedGeneration<T, R>[] = [];
|
|
613
|
+
// Fence the peer from every active generation, including generations that
|
|
614
|
+
// have not emitted their first request yet. A removal can begin between
|
|
615
|
+
// generation creation and message emission.
|
|
616
|
+
for (const [hash, session] of this.sessions) {
|
|
617
|
+
const pending = session.pending;
|
|
618
|
+
if (pending && this.pendingDeletes.get(hash) === pending) {
|
|
619
|
+
session.revoked.add(peer);
|
|
620
|
+
const candidate = this.getRestartCandidate(hash);
|
|
621
|
+
if (
|
|
622
|
+
candidate &&
|
|
623
|
+
(session.contacted.has(peer) || candidate.leaders.has(peer))
|
|
624
|
+
) {
|
|
625
|
+
invalidated.push({
|
|
626
|
+
...candidate,
|
|
627
|
+
pending,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
for (const [hash, peers] of [...this.requestIPruneSent]) {
|
|
634
|
+
if (peers.has(peer)) {
|
|
635
|
+
this.removeRequestSent(hash, peer);
|
|
329
636
|
}
|
|
330
637
|
}
|
|
331
638
|
|
|
332
|
-
for (const [hash, peers] of this.responseReplicatorSet) {
|
|
333
|
-
peers.
|
|
334
|
-
|
|
335
|
-
if (peers.size === 0) {
|
|
336
|
-
this.responseReplicatorSet.delete(hash);
|
|
337
|
-
this.deleteSessionIfIdle(hash);
|
|
639
|
+
for (const [hash, peers] of [...this.responseReplicatorSet]) {
|
|
640
|
+
if (peers.has(peer)) {
|
|
641
|
+
this.removeConfirmedReplicator(hash, peer);
|
|
338
642
|
}
|
|
339
643
|
}
|
|
644
|
+
return invalidated;
|
|
340
645
|
}
|
|
341
646
|
|
|
342
647
|
close() {
|
|
@@ -354,5 +659,9 @@ export class CheckedPruneCoordinator<T, R extends "u32" | "u64"> {
|
|
|
354
659
|
this.responseReplicatorSet.clear();
|
|
355
660
|
this.retries.clear();
|
|
356
661
|
this.sessions.clear();
|
|
662
|
+
this.grantSends.clear();
|
|
663
|
+
this.peerRemovalFences.clear();
|
|
664
|
+
this.restartReservations.clear();
|
|
665
|
+
this.candidateTokens.clear();
|
|
357
666
|
}
|
|
358
667
|
}
|
package/src/exchange-heads.ts
CHANGED
|
@@ -1106,6 +1106,58 @@ export class ResponseIPrune extends TransportMessage {
|
|
|
1106
1106
|
}
|
|
1107
1107
|
}
|
|
1108
1108
|
|
|
1109
|
+
@variant(0)
|
|
1110
|
+
export class CheckedPruneRequest {
|
|
1111
|
+
@field({ type: "string" })
|
|
1112
|
+
hash: string;
|
|
1113
|
+
|
|
1114
|
+
@field({ type: fixedArray("u8", 32) })
|
|
1115
|
+
requestId: Uint8Array;
|
|
1116
|
+
|
|
1117
|
+
constructor(props: { hash: string; requestId: Uint8Array }) {
|
|
1118
|
+
this.hash = props.hash;
|
|
1119
|
+
this.requestId = props.requestId;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
@variant([0, 11])
|
|
1124
|
+
export class RequestIPruneV2 extends TransportMessage {
|
|
1125
|
+
@field({ type: vec(CheckedPruneRequest) })
|
|
1126
|
+
requests: CheckedPruneRequest[];
|
|
1127
|
+
|
|
1128
|
+
constructor(props: {
|
|
1129
|
+
requests: Array<
|
|
1130
|
+
CheckedPruneRequest | { hash: string; requestId: Uint8Array }
|
|
1131
|
+
>;
|
|
1132
|
+
}) {
|
|
1133
|
+
super();
|
|
1134
|
+
this.requests = props.requests.map((request) =>
|
|
1135
|
+
request instanceof CheckedPruneRequest
|
|
1136
|
+
? request
|
|
1137
|
+
: new CheckedPruneRequest(request),
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
@variant([0, 12])
|
|
1143
|
+
export class ResponseIPruneV2 extends TransportMessage {
|
|
1144
|
+
@field({ type: vec(CheckedPruneRequest) })
|
|
1145
|
+
requests: CheckedPruneRequest[];
|
|
1146
|
+
|
|
1147
|
+
constructor(props: {
|
|
1148
|
+
requests: Array<
|
|
1149
|
+
CheckedPruneRequest | { hash: string; requestId: Uint8Array }
|
|
1150
|
+
>;
|
|
1151
|
+
}) {
|
|
1152
|
+
super();
|
|
1153
|
+
this.requests = props.requests.map((request) =>
|
|
1154
|
+
request instanceof CheckedPruneRequest
|
|
1155
|
+
? request
|
|
1156
|
+
: new CheckedPruneRequest(request),
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1109
1161
|
const MAX_EXCHANGE_MESSAGE_SIZE = 1e5; // 100kb. Too large size might not be faster (even if we can do 5mb)
|
|
1110
1162
|
export const MAX_RAW_EXCHANGE_MESSAGE_SIZE = 512 * 1024;
|
|
1111
1163
|
export const EXCHANGE_HEADS_RESOLVE_BATCH_SIZE = 256;
|
|
@@ -1204,21 +1256,19 @@ export const createExchangeHeadsMessages = async function* (
|
|
|
1204
1256
|
}
|
|
1205
1257
|
|
|
1206
1258
|
// Fallback for logs without native reference rows.
|
|
1207
|
-
const
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
return true;
|
|
1213
|
-
});
|
|
1259
|
+
const gidRefrences = await collectFallbackUniqueReferenceGids(
|
|
1260
|
+
log,
|
|
1261
|
+
entry,
|
|
1262
|
+
visitedHeads,
|
|
1263
|
+
);
|
|
1214
1264
|
|
|
1215
|
-
if (
|
|
1216
|
-
warn("Large refs count: ",
|
|
1265
|
+
if (gidRefrences.length > 1000) {
|
|
1266
|
+
warn("Large refs count: ", gidRefrences.length);
|
|
1217
1267
|
}
|
|
1218
1268
|
current.push(
|
|
1219
1269
|
new EntryWithRefs({
|
|
1220
1270
|
entry,
|
|
1221
|
-
gidRefrences
|
|
1271
|
+
gidRefrences,
|
|
1222
1272
|
}),
|
|
1223
1273
|
);
|
|
1224
1274
|
|
|
@@ -2096,18 +2146,18 @@ const resolveExchangeHeadEntries = async (
|
|
|
2096
2146
|
return resolved;
|
|
2097
2147
|
};
|
|
2098
2148
|
|
|
2099
|
-
|
|
2149
|
+
const collectFallbackUniqueReferenceGids = async (
|
|
2100
2150
|
log: Log<any>,
|
|
2101
2151
|
entry: Entry<any>,
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
const
|
|
2152
|
+
visitedHeads: Set<string>,
|
|
2153
|
+
): Promise<string[]> => {
|
|
2154
|
+
const hashByGid = new Map<string, string>();
|
|
2105
2155
|
let curr: (Entry<any> | ShallowEntry)[] = [entry];
|
|
2106
2156
|
while (curr.length > 0) {
|
|
2107
2157
|
const nexts: (Entry<any> | ShallowEntry)[] = [];
|
|
2108
2158
|
for (const element of curr) {
|
|
2109
|
-
if (!
|
|
2110
|
-
|
|
2159
|
+
if (!hashByGid.has(element.meta.gid)) {
|
|
2160
|
+
hashByGid.set(element.meta.gid, element.hash);
|
|
2111
2161
|
if (element.meta.type === EntryType.APPEND) {
|
|
2112
2162
|
for (const next of element.meta.next) {
|
|
2113
2163
|
const indexedEntry = await log.entryIndex.getShallow(next);
|
|
@@ -2122,30 +2172,17 @@ export const allEntriesWithUniqueGids = async (
|
|
|
2122
2172
|
}
|
|
2123
2173
|
}
|
|
2124
2174
|
}
|
|
2125
|
-
curr = nexts;
|
|
2126
2175
|
}
|
|
2176
|
+
curr = nexts;
|
|
2127
2177
|
}
|
|
2128
|
-
|
|
2129
|
-
const
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2132
|
-
for (let i = 0; i < values.length; i++) {
|
|
2133
|
-
const value = values[i]!;
|
|
2134
|
-
if (value instanceof Entry) {
|
|
2135
|
-
resolved[i] = value;
|
|
2178
|
+
|
|
2179
|
+
const gidRefrences: string[] = [];
|
|
2180
|
+
for (const [gid, hash] of hashByGid) {
|
|
2181
|
+
if (visitedHeads.has(hash)) {
|
|
2136
2182
|
continue;
|
|
2137
2183
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
}
|
|
2141
|
-
if (unresolvedHashes.length > 0) {
|
|
2142
|
-
const entries = await log.entryIndex.getMany(unresolvedHashes, {
|
|
2143
|
-
type: "full",
|
|
2144
|
-
ignoreMissing: true,
|
|
2145
|
-
});
|
|
2146
|
-
for (let i = 0; i < entries.length; i++) {
|
|
2147
|
-
resolved[unresolvedPositions[i]!] = entries[i];
|
|
2148
|
-
}
|
|
2184
|
+
visitedHeads.add(hash);
|
|
2185
|
+
gidRefrences.push(gid);
|
|
2149
2186
|
}
|
|
2150
|
-
return
|
|
2187
|
+
return gidRefrences;
|
|
2151
2188
|
};
|