@peerbit/shared-log 13.2.24 → 13.2.26
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/src/checked-prune.d.ts +12 -0
- package/dist/src/checked-prune.d.ts.map +1 -1
- package/dist/src/checked-prune.js +33 -0
- package/dist/src/checked-prune.js.map +1 -1
- package/dist/src/coordinate-persistence.d.ts +211 -0
- package/dist/src/coordinate-persistence.d.ts.map +1 -0
- package/dist/src/coordinate-persistence.js +1133 -0
- package/dist/src/coordinate-persistence.js.map +1 -0
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +19 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +127 -133
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1400 -4269
- package/dist/src/index.js.map +1 -1
- package/dist/src/instance-lifecycle.d.ts +117 -0
- package/dist/src/instance-lifecycle.d.ts.map +1 -0
- package/dist/src/instance-lifecycle.js +254 -0
- package/dist/src/instance-lifecycle.js.map +1 -0
- package/dist/src/join-warmup.d.ts +86 -0
- package/dist/src/join-warmup.d.ts.map +1 -0
- package/dist/src/join-warmup.js +351 -0
- package/dist/src/join-warmup.js.map +1 -0
- package/dist/src/native-write-through-block-store.d.ts +123 -0
- package/dist/src/native-write-through-block-store.d.ts.map +1 -0
- package/dist/src/native-write-through-block-store.js +989 -0
- package/dist/src/native-write-through-block-store.js.map +1 -0
- package/dist/src/peer-session.d.ts +117 -0
- package/dist/src/peer-session.d.ts.map +1 -0
- package/dist/src/peer-session.js +259 -0
- package/dist/src/peer-session.js.map +1 -0
- package/dist/src/replication-announcement.d.ts +116 -0
- package/dist/src/replication-announcement.d.ts.map +1 -0
- package/dist/src/replication-announcement.js +515 -0
- package/dist/src/replication-announcement.js.map +1 -0
- package/dist/src/replicator-liveness.d.ts +59 -0
- package/dist/src/replicator-liveness.d.ts.map +1 -0
- package/dist/src/replicator-liveness.js +304 -0
- package/dist/src/replicator-liveness.js.map +1 -0
- package/dist/src/sync/dispatch-lifecycle.d.ts +48 -0
- package/dist/src/sync/dispatch-lifecycle.d.ts.map +1 -0
- package/dist/src/sync/dispatch-lifecycle.js +141 -0
- package/dist/src/sync/dispatch-lifecycle.js.map +1 -0
- package/dist/src/sync/factory.d.ts +29 -0
- package/dist/src/sync/factory.d.ts.map +1 -0
- package/dist/src/sync/factory.js +87 -0
- package/dist/src/sync/factory.js.map +1 -0
- package/dist/src/sync/pending-sync-store.d.ts +105 -0
- package/dist/src/sync/pending-sync-store.d.ts.map +1 -0
- package/dist/src/sync/pending-sync-store.js +877 -0
- package/dist/src/sync/pending-sync-store.js.map +1 -0
- package/dist/src/sync/rateless-iblt.d.ts +4 -1
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +102 -106
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +10 -41
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +259 -952
- package/dist/src/sync/simple.js.map +1 -1
- package/dist/src/sync/sync-peer-state.d.ts +16 -0
- package/dist/src/sync/sync-peer-state.d.ts.map +1 -0
- package/dist/src/sync/sync-peer-state.js +82 -0
- package/dist/src/sync/sync-peer-state.js.map +1 -0
- package/package.json +12 -12
- package/src/checked-prune.ts +35 -0
- package/src/coordinate-persistence.ts +1796 -0
- package/src/errors.ts +21 -0
- package/src/index.ts +4014 -7858
- package/src/instance-lifecycle.ts +351 -0
- package/src/join-warmup.ts +507 -0
- package/src/native-write-through-block-store.ts +1170 -0
- package/src/peer-session.ts +336 -0
- package/src/replication-announcement.ts +744 -0
- package/src/replicator-liveness.ts +439 -0
- package/src/sync/dispatch-lifecycle.ts +230 -0
- package/src/sync/factory.ts +154 -0
- package/src/sync/pending-sync-store.ts +1085 -0
- package/src/sync/rateless-iblt.ts +122 -132
- package/src/sync/simple.ts +334 -1144
- package/src/sync/sync-peer-state.ts +107 -0
package/src/sync/simple.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "../exchange-heads.js";
|
|
21
21
|
import { TransportMessage } from "../message.js";
|
|
22
22
|
import type { EntryReplicated } from "../ranges.js";
|
|
23
|
+
import { DispatchLifecycleRegistry } from "./dispatch-lifecycle.js";
|
|
23
24
|
import type {
|
|
24
25
|
HashSymbolHashListResolver,
|
|
25
26
|
HashSymbolResolver,
|
|
@@ -32,7 +33,16 @@ import type {
|
|
|
32
33
|
SyncableKey,
|
|
33
34
|
Syncronizer,
|
|
34
35
|
} from "./index.js";
|
|
36
|
+
import {
|
|
37
|
+
type PendingSyncAdmissionReservation,
|
|
38
|
+
PendingSyncStore,
|
|
39
|
+
QUEUED_SYNC_ALIAS_REFRESH_PENDING,
|
|
40
|
+
} from "./pending-sync-store.js";
|
|
35
41
|
import { emitSyncProfileDuration, syncProfileStart } from "./profile.js";
|
|
42
|
+
import {
|
|
43
|
+
SyncPeerSlotRegistry,
|
|
44
|
+
type SyncPeerSlotRow,
|
|
45
|
+
} from "./sync-peer-state.js";
|
|
36
46
|
|
|
37
47
|
@variant([0, 1])
|
|
38
48
|
export class RequestMaybeSync extends TransportMessage {
|
|
@@ -332,16 +342,10 @@ export const MAX_PENDING_SIMPLE_SYNC_LOOKUPS_GLOBAL = 32;
|
|
|
332
342
|
// Retry scanning can touch persistent indexes. Bound each pass so a full
|
|
333
343
|
// adversarial queue cannot force 40,000 lookups in one event-loop turn.
|
|
334
344
|
export const MAX_SIMPLE_SYNC_RETRY_KEYS_PER_TICK = 4_096;
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
const QUEUED_SYNC_ALIAS_REFRESH_PENDING = Symbol(
|
|
340
|
-
"queued-sync-alias-refresh-pending",
|
|
341
|
-
);
|
|
342
|
-
// This is an absolute first-seen lifetime. Repeated claims and additional peers
|
|
343
|
-
// deliberately do not slide the deadline.
|
|
344
|
-
export const PENDING_SIMPLE_SYNC_KEY_TTL_MS = 60_000;
|
|
345
|
+
// The pending-claim TTL, alias-refresh bound and pending-refresh sentinel
|
|
346
|
+
// moved into ./pending-sync-store.ts with the record store (stage-4 sync
|
|
347
|
+
// unification). Re-exported here for compatibility.
|
|
348
|
+
export { PENDING_SIMPLE_SYNC_KEY_TTL_MS } from "./pending-sync-store.js";
|
|
345
349
|
// Bound retained request/response associations globally. Ten thousand hashes
|
|
346
350
|
// covers several full default-size request batches while keeping adversarial or
|
|
347
351
|
// abandoned requests to a small, predictable amount of heap.
|
|
@@ -351,30 +355,6 @@ export const MAX_ACTIVE_SIMPLE_SYNC_RESPONSES_GLOBAL = 32;
|
|
|
351
355
|
const MAX_PENDING_MAYBE_SYNC_RESPONSE_WAITER_BYPASSES = 32;
|
|
352
356
|
const MAX_PENDING_MAYBE_SYNC_RESPONSE_WAITERS = 10_000;
|
|
353
357
|
|
|
354
|
-
type PendingSyncAdmissionReservation = {
|
|
355
|
-
peer: string;
|
|
356
|
-
remaining: number;
|
|
357
|
-
active: boolean;
|
|
358
|
-
released: boolean;
|
|
359
|
-
expiresAt: number;
|
|
360
|
-
identities: Set<SyncableKey>;
|
|
361
|
-
retainedSettled: number;
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
type PendingSyncExpiryNode =
|
|
365
|
-
| {
|
|
366
|
-
kind: "key";
|
|
367
|
-
key: SyncableKey;
|
|
368
|
-
expiresAt: number;
|
|
369
|
-
heapIndex: number;
|
|
370
|
-
}
|
|
371
|
-
| {
|
|
372
|
-
kind: "admission";
|
|
373
|
-
reservation: PendingSyncAdmissionReservation;
|
|
374
|
-
expiresAt: number;
|
|
375
|
-
heapIndex: number;
|
|
376
|
-
};
|
|
377
|
-
|
|
378
358
|
type PendingMaybeSyncResponse = {
|
|
379
359
|
hashes: Set<string>;
|
|
380
360
|
target: string;
|
|
@@ -396,6 +376,12 @@ type PendingMaybeSyncResponseAuthorization = {
|
|
|
396
376
|
deliveryInFlight?: boolean;
|
|
397
377
|
settled?: "fulfilled" | "released";
|
|
398
378
|
active?: boolean;
|
|
379
|
+
// Set when consume accepts the authorization: the peer slot row that holds
|
|
380
|
+
// this authorization's pending-response hash-budget unit whenever
|
|
381
|
+
// deliveryInFlight !== true (deliveryInFlight custody belongs to
|
|
382
|
+
// finishDelivery). Row identity, not peer hash: a late settle never
|
|
383
|
+
// touches a reconnected successor's row.
|
|
384
|
+
slotRow?: SyncPeerSlotRow;
|
|
399
385
|
};
|
|
400
386
|
|
|
401
387
|
type PendingMaybeSyncResponseReservation = {
|
|
@@ -498,53 +484,40 @@ type RepairSessionState = {
|
|
|
498
484
|
export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
499
485
|
implements Syncronizer<R>
|
|
500
486
|
{
|
|
501
|
-
//
|
|
502
|
-
syncInFlightQueue
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
private
|
|
506
|
-
private pendingSyncExpiryHeap: PendingSyncExpiryNode[];
|
|
507
|
-
private pendingSyncKeyExpiryNodes: Map<SyncableKey, PendingSyncExpiryNode>;
|
|
508
|
-
private pendingSyncAdmissionExpiryNodes: Map<
|
|
509
|
-
PendingSyncAdmissionReservation,
|
|
510
|
-
PendingSyncExpiryNode
|
|
511
|
-
>;
|
|
487
|
+
// The pending-sync record store owns the claim/admission state. The
|
|
488
|
+
// legacy public map instances (syncInFlightQueue, syncInFlightQueueInverted,
|
|
489
|
+
// syncInFlight, ...) live inside it and are exposed through the accessors
|
|
490
|
+
// below under their historical names.
|
|
491
|
+
private readonly pendingSync: PendingSyncStore;
|
|
512
492
|
private syncInFlightRetryIterator?: IterableIterator<
|
|
513
493
|
[SyncableKey, PublicSignKey[]]
|
|
514
494
|
>;
|
|
515
495
|
private syncInFlightRetryRemaining = 0;
|
|
516
|
-
private syncInFlightQueueClaimants: Map<SyncableKey, Set<string>>;
|
|
517
|
-
private syncInFlightQueueClaimantIndexes: Map<
|
|
518
|
-
SyncableKey,
|
|
519
|
-
Map<string, number>
|
|
520
|
-
>;
|
|
521
|
-
private syncInFlightQueueRoundRobinCursor: Map<SyncableKey, number>;
|
|
522
|
-
private syncInFlightQueuedCoordinates: Set<bigint>;
|
|
523
|
-
private syncInFlightQueuedHashByCoordinate: Map<bigint, string>;
|
|
524
|
-
private syncInFlightQueuedCoordinatesByHash: Map<string, Set<bigint>>;
|
|
525
|
-
private syncInFlightQueuedCoordinateRefreshIterator?: IterableIterator<bigint>;
|
|
526
|
-
private pendingSyncClaimCount: number;
|
|
527
|
-
private pendingSyncAdmissionCount: number;
|
|
528
|
-
private pendingSyncActiveAdmissionReservations: number;
|
|
529
|
-
private pendingSyncAdmissionCountByPeer: Map<string, number>;
|
|
530
|
-
private pendingSyncAdmissionIdentitiesByPeer: Map<string, Set<SyncableKey>>;
|
|
531
|
-
private pendingSyncAdmissionReservations: Set<PendingSyncAdmissionReservation>;
|
|
532
|
-
private pendingSyncAdmissionReservationsByPeer: Map<
|
|
533
|
-
string,
|
|
534
|
-
Set<PendingSyncAdmissionReservation>
|
|
535
|
-
>;
|
|
536
|
-
private pendingSyncAdmissionReservationsByIdentity: Map<
|
|
537
|
-
SyncableKey,
|
|
538
|
-
Set<PendingSyncAdmissionReservation>
|
|
539
|
-
>;
|
|
540
496
|
private pendingCoordinateLookupCount: number;
|
|
541
497
|
private pendingCoordinateLookupCountByPeer: Map<string, number>;
|
|
542
498
|
private pendingCoordinateResponseCount: number;
|
|
543
499
|
private pendingCoordinateResponseCountByPeer: Map<string, number>;
|
|
500
|
+
// Per-peer slot rows for coordinate lookups, coordinate responses and
|
|
501
|
+
// active maybe-sync responses (shared registry; see sync-peer-state.ts
|
|
502
|
+
// for the lifetime policy). Release closures capture the row object:
|
|
503
|
+
// once a disconnect detaches the row, the peer's slots return to the
|
|
504
|
+
// aggregate quota immediately, and a late release only settles the
|
|
505
|
+
// detached row — it never touches a reconnected successor's quota.
|
|
506
|
+
private readonly peerSlotRows: SyncPeerSlotRegistry;
|
|
507
|
+
|
|
508
|
+
// map of hash to public keys that we can ask for entries
|
|
509
|
+
get syncInFlightQueue(): Map<SyncableKey, PublicSignKey[]> {
|
|
510
|
+
return this.pendingSync.syncInFlightQueue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
get syncInFlightQueueInverted(): Map<string, Set<SyncableKey>> {
|
|
514
|
+
return this.pendingSync.syncInFlightQueueInverted;
|
|
515
|
+
}
|
|
544
516
|
|
|
545
517
|
// map of hash to public keys that we have asked for entries
|
|
546
|
-
syncInFlight
|
|
547
|
-
|
|
518
|
+
get syncInFlight(): Map<string, Map<SyncableKey, { timestamp: number }>> {
|
|
519
|
+
return this.pendingSync.syncInFlight;
|
|
520
|
+
}
|
|
548
521
|
|
|
549
522
|
rpc: RPC<TransportMessage, TransportMessage>;
|
|
550
523
|
log: Log<any>;
|
|
@@ -581,7 +554,11 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
581
554
|
private syncDispatchLifecycleController: AbortController;
|
|
582
555
|
private syncDispatchTargetEpochCounter: number;
|
|
583
556
|
private syncDispatchTargetEpochs: Map<string, SyncDispatchTargetEpoch>;
|
|
584
|
-
private
|
|
557
|
+
private readonly syncDispatchRegistry: DispatchLifecycleRegistry<
|
|
558
|
+
SyncDispatchLifecycle,
|
|
559
|
+
SyncDispatchTargetLifecycle
|
|
560
|
+
>;
|
|
561
|
+
|
|
585
562
|
private repairSessionCounter: number;
|
|
586
563
|
private repairSessions: Map<string, RepairSessionState>;
|
|
587
564
|
|
|
@@ -606,32 +583,16 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
606
583
|
peerSupportsRawExchangeHeads?: (peer: string) => boolean;
|
|
607
584
|
sendRawExchangeHeads?: RawExchangeHeadsSender;
|
|
608
585
|
}) {
|
|
609
|
-
this.
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
this.pendingSyncAdmissionExpiryNodes = new Map();
|
|
615
|
-
this.syncInFlightQueueClaimants = new Map();
|
|
616
|
-
this.syncInFlightQueueClaimantIndexes = new Map();
|
|
617
|
-
this.syncInFlightQueueRoundRobinCursor = new Map();
|
|
618
|
-
this.syncInFlightQueuedCoordinates = new Set();
|
|
619
|
-
this.syncInFlightQueuedHashByCoordinate = new Map();
|
|
620
|
-
this.syncInFlightQueuedCoordinatesByHash = new Map();
|
|
621
|
-
this.pendingSyncClaimCount = 0;
|
|
622
|
-
this.pendingSyncAdmissionCount = 0;
|
|
623
|
-
this.pendingSyncActiveAdmissionReservations = 0;
|
|
624
|
-
this.pendingSyncAdmissionCountByPeer = new Map();
|
|
625
|
-
this.pendingSyncAdmissionIdentitiesByPeer = new Map();
|
|
626
|
-
this.pendingSyncAdmissionReservations = new Set();
|
|
627
|
-
this.pendingSyncAdmissionReservationsByPeer = new Map();
|
|
628
|
-
this.pendingSyncAdmissionReservationsByIdentity = new Map();
|
|
586
|
+
this.pendingSync = new PendingSyncStore({
|
|
587
|
+
coordinateToHash: properties.coordinateToHash,
|
|
588
|
+
isDispatchEpochCurrent: (peer, epoch) =>
|
|
589
|
+
this.syncDispatchTargetEpochs.get(peer) === epoch,
|
|
590
|
+
});
|
|
629
591
|
this.pendingCoordinateLookupCount = 0;
|
|
630
592
|
this.pendingCoordinateLookupCountByPeer = new Map();
|
|
631
593
|
this.pendingCoordinateResponseCount = 0;
|
|
632
594
|
this.pendingCoordinateResponseCountByPeer = new Map();
|
|
633
|
-
this.
|
|
634
|
-
this.syncInFlightTargetsByKey = new Map();
|
|
595
|
+
this.peerSlotRows = new SyncPeerSlotRegistry();
|
|
635
596
|
this.rpc = properties.rpc;
|
|
636
597
|
this.log = properties.log;
|
|
637
598
|
this.entryIndex = properties.entryIndex;
|
|
@@ -659,7 +620,24 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
659
620
|
this.syncDispatchLifecycleController = new AbortController();
|
|
660
621
|
this.syncDispatchTargetEpochCounter = 0;
|
|
661
622
|
this.syncDispatchTargetEpochs = new Map();
|
|
662
|
-
this.
|
|
623
|
+
this.syncDispatchRegistry = new DispatchLifecycleRegistry<
|
|
624
|
+
SyncDispatchLifecycle,
|
|
625
|
+
SyncDispatchTargetLifecycle
|
|
626
|
+
>({
|
|
627
|
+
isClosed: () => this.closed === true,
|
|
628
|
+
currentOwnershipLifecycleController: () =>
|
|
629
|
+
this.syncDispatchLifecycleController,
|
|
630
|
+
hasRetainedWork: (lifecycle) => lifecycle.retainedWork > 0,
|
|
631
|
+
// Target-activity strategy: dispatch-epoch identity.
|
|
632
|
+
isTargetCurrent: (targetLifecycle) =>
|
|
633
|
+
this.syncDispatchTargetEpochs.get(targetLifecycle.target) ===
|
|
634
|
+
targetLifecycle.epoch,
|
|
635
|
+
onTargetAborted: (targetLifecycle) => {
|
|
636
|
+
for (const batch of [...targetLifecycle.batches]) {
|
|
637
|
+
this.removePendingMaybeSyncResponseBatch(batch);
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
});
|
|
663
641
|
this.repairSessionCounter = 0;
|
|
664
642
|
this.repairSessions = new Map();
|
|
665
643
|
}
|
|
@@ -839,60 +817,43 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
839
817
|
abortAllOnTargetDisconnect: options?.abortAllOnTargetDisconnect === true,
|
|
840
818
|
} satisfies SyncDispatchLifecycle;
|
|
841
819
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
expectedEpoch
|
|
847
|
-
currentEpoch
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
ownershipLifecycleController.signal.addEventListener(
|
|
879
|
-
"abort",
|
|
880
|
-
lifecycle.onOwnerOrCallerAbort,
|
|
881
|
-
{ once: true },
|
|
820
|
+
this.syncDispatchRegistry.register(
|
|
821
|
+
lifecycle,
|
|
822
|
+
targets,
|
|
823
|
+
(forLifecycle, target) => {
|
|
824
|
+
const expectedEpoch = options?.targetEpochs?.get(target);
|
|
825
|
+
const currentEpoch = this.syncDispatchTargetEpochs.get(target);
|
|
826
|
+
const epoch =
|
|
827
|
+
expectedEpoch ??
|
|
828
|
+
currentEpoch ??
|
|
829
|
+
(options?.createTargetEpochs === false
|
|
830
|
+
? undefined
|
|
831
|
+
: this.getOrCreateSyncDispatchTargetEpoch(target));
|
|
832
|
+
if (!epoch) {
|
|
833
|
+
return undefined;
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
lifecycle: forLifecycle,
|
|
837
|
+
target,
|
|
838
|
+
epoch,
|
|
839
|
+
controller: new AbortController(),
|
|
840
|
+
batches: new Set(),
|
|
841
|
+
responseLeases: 0,
|
|
842
|
+
activeWaiters: 0,
|
|
843
|
+
};
|
|
844
|
+
},
|
|
845
|
+
(targetLifecycle) => {
|
|
846
|
+
if (
|
|
847
|
+
this.syncDispatchTargetEpochs.get(targetLifecycle.target) !==
|
|
848
|
+
targetLifecycle.epoch
|
|
849
|
+
) {
|
|
850
|
+
this.abortSyncDispatchTarget(
|
|
851
|
+
targetLifecycle,
|
|
852
|
+
new Error("sync target lifecycle is stale"),
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
},
|
|
882
856
|
);
|
|
883
|
-
if (callerSignal && callerSignal !== ownershipLifecycleController.signal) {
|
|
884
|
-
callerSignal.addEventListener("abort", lifecycle.onOwnerOrCallerAbort, {
|
|
885
|
-
once: true,
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
if (
|
|
889
|
-
this.closed === true ||
|
|
890
|
-
ownershipLifecycleController !== this.syncDispatchLifecycleController ||
|
|
891
|
-
ownershipLifecycleController.signal.aborted ||
|
|
892
|
-
callerSignal?.aborted
|
|
893
|
-
) {
|
|
894
|
-
lifecycle.onOwnerOrCallerAbort();
|
|
895
|
-
}
|
|
896
857
|
return lifecycle;
|
|
897
858
|
}
|
|
898
859
|
|
|
@@ -900,102 +861,38 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
900
861
|
targetLifecycle: SyncDispatchTargetLifecycle,
|
|
901
862
|
reason?: unknown,
|
|
902
863
|
): void {
|
|
903
|
-
|
|
904
|
-
targetLifecycle.controller.abort(reason);
|
|
905
|
-
}
|
|
906
|
-
for (const batch of [...targetLifecycle.batches]) {
|
|
907
|
-
this.removePendingMaybeSyncResponseBatch(batch);
|
|
908
|
-
}
|
|
909
|
-
this.maybeDisposeSyncDispatchLifecycle(targetLifecycle.lifecycle);
|
|
864
|
+
this.syncDispatchRegistry.abortTarget(targetLifecycle, reason);
|
|
910
865
|
}
|
|
911
866
|
|
|
912
867
|
private abortSyncDispatchLifecycle(
|
|
913
868
|
lifecycle: SyncDispatchLifecycle,
|
|
914
869
|
reason?: unknown,
|
|
915
870
|
): void {
|
|
916
|
-
|
|
917
|
-
lifecycle.controller.abort(reason);
|
|
918
|
-
}
|
|
919
|
-
for (const targetLifecycle of lifecycle.targets.values()) {
|
|
920
|
-
this.abortSyncDispatchTarget(targetLifecycle, reason);
|
|
921
|
-
}
|
|
922
|
-
this.maybeDisposeSyncDispatchLifecycle(lifecycle);
|
|
871
|
+
this.syncDispatchRegistry.abortLifecycle(lifecycle, reason);
|
|
923
872
|
}
|
|
924
873
|
|
|
925
874
|
private finishSyncDispatchLifecycle(lifecycle: SyncDispatchLifecycle): void {
|
|
926
|
-
lifecycle
|
|
927
|
-
this.maybeDisposeSyncDispatchLifecycle(lifecycle);
|
|
875
|
+
this.syncDispatchRegistry.finish(lifecycle);
|
|
928
876
|
}
|
|
929
877
|
|
|
930
878
|
private maybeDisposeSyncDispatchLifecycle(
|
|
931
879
|
lifecycle: SyncDispatchLifecycle,
|
|
932
880
|
): void {
|
|
933
|
-
|
|
934
|
-
lifecycle.disposed ||
|
|
935
|
-
!lifecycle.dispatchFinished ||
|
|
936
|
-
lifecycle.retainedWork > 0
|
|
937
|
-
) {
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
lifecycle.disposed = true;
|
|
941
|
-
lifecycle.ownershipLifecycleController.signal.removeEventListener(
|
|
942
|
-
"abort",
|
|
943
|
-
lifecycle.onOwnerOrCallerAbort,
|
|
944
|
-
);
|
|
945
|
-
if (
|
|
946
|
-
lifecycle.callerSignal &&
|
|
947
|
-
lifecycle.callerSignal !== lifecycle.ownershipLifecycleController.signal
|
|
948
|
-
) {
|
|
949
|
-
lifecycle.callerSignal.removeEventListener(
|
|
950
|
-
"abort",
|
|
951
|
-
lifecycle.onOwnerOrCallerAbort,
|
|
952
|
-
);
|
|
953
|
-
}
|
|
954
|
-
for (const targetLifecycle of lifecycle.targets.values()) {
|
|
955
|
-
const activeForTarget = this.syncDispatchTargets.get(
|
|
956
|
-
targetLifecycle.target,
|
|
957
|
-
);
|
|
958
|
-
activeForTarget?.delete(targetLifecycle);
|
|
959
|
-
if (activeForTarget?.size === 0) {
|
|
960
|
-
this.syncDispatchTargets.delete(targetLifecycle.target);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
881
|
+
this.syncDispatchRegistry.maybeDispose(lifecycle);
|
|
963
882
|
}
|
|
964
883
|
|
|
965
884
|
private isSyncDispatchLifecycleActive(
|
|
966
885
|
lifecycle: SyncDispatchLifecycle,
|
|
967
886
|
target?: string,
|
|
968
887
|
): boolean {
|
|
969
|
-
|
|
970
|
-
this.closed === true ||
|
|
971
|
-
lifecycle.disposed ||
|
|
972
|
-
lifecycle.ownershipLifecycleController !==
|
|
973
|
-
this.syncDispatchLifecycleController ||
|
|
974
|
-
lifecycle.ownershipLifecycleController.signal.aborted ||
|
|
975
|
-
lifecycle.callerSignal?.aborted ||
|
|
976
|
-
lifecycle.controller.signal.aborted
|
|
977
|
-
) {
|
|
978
|
-
return false;
|
|
979
|
-
}
|
|
980
|
-
if (target === undefined) {
|
|
981
|
-
return true;
|
|
982
|
-
}
|
|
983
|
-
const targetLifecycle = lifecycle.targets.get(target);
|
|
984
|
-
return (
|
|
985
|
-
targetLifecycle !== undefined &&
|
|
986
|
-
!targetLifecycle.controller.signal.aborted &&
|
|
987
|
-
this.syncDispatchTargetEpochs.get(target) === targetLifecycle.epoch
|
|
988
|
-
);
|
|
888
|
+
return this.syncDispatchRegistry.isLifecycleActive(lifecycle, target);
|
|
989
889
|
}
|
|
990
890
|
|
|
991
891
|
private getSyncDispatchSignal(
|
|
992
892
|
lifecycle: SyncDispatchLifecycle,
|
|
993
893
|
target: string,
|
|
994
894
|
): AbortSignal {
|
|
995
|
-
return (
|
|
996
|
-
lifecycle.targets.get(target)?.controller.signal ??
|
|
997
|
-
lifecycle.controller.signal
|
|
998
|
-
);
|
|
895
|
+
return this.syncDispatchRegistry.getSignal(lifecycle, target);
|
|
999
896
|
}
|
|
1000
897
|
|
|
1001
898
|
private pendingMaybeSyncResponseWaiterBefore(
|
|
@@ -1697,6 +1594,16 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
1697
1594
|
beginDelivery: () => {
|
|
1698
1595
|
for (const authorization of addedAuthorizations) {
|
|
1699
1596
|
if (!authorization.settled) {
|
|
1597
|
+
if (
|
|
1598
|
+
authorization.deliveryInFlight !== true &&
|
|
1599
|
+
authorization.active === true &&
|
|
1600
|
+
authorization.slotRow?.attached === true
|
|
1601
|
+
) {
|
|
1602
|
+
// The response was accepted before the request send began:
|
|
1603
|
+
// custody of the hash-budget unit moves from the slot row to
|
|
1604
|
+
// finishDelivery for the duration of the send.
|
|
1605
|
+
authorization.slotRow.pendingResponseHashes -= 1;
|
|
1606
|
+
}
|
|
1700
1607
|
authorization.deliveryInFlight = true;
|
|
1701
1608
|
}
|
|
1702
1609
|
}
|
|
@@ -1715,6 +1622,14 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
1715
1622
|
?.get(authorization.hash) !== authorization
|
|
1716
1623
|
) {
|
|
1717
1624
|
releasedCount += 1;
|
|
1625
|
+
} else if (
|
|
1626
|
+
authorization.active === true &&
|
|
1627
|
+
authorization.slotRow?.attached === true
|
|
1628
|
+
) {
|
|
1629
|
+
// The response was accepted while the request send was in
|
|
1630
|
+
// flight; custody of the hash-budget unit returns to the
|
|
1631
|
+
// active lease's slot row.
|
|
1632
|
+
authorization.slotRow.pendingResponseHashes += 1;
|
|
1718
1633
|
}
|
|
1719
1634
|
}
|
|
1720
1635
|
if (releasedCount > 0) {
|
|
@@ -2048,45 +1963,74 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
2048
1963
|
if (acceptedByLifecycle.size === 0) {
|
|
2049
1964
|
return [];
|
|
2050
1965
|
}
|
|
1966
|
+
const slotRow = this.getOrCreateSyncResponseSlotRow(fromHash);
|
|
1967
|
+
slotRow.active += 1;
|
|
2051
1968
|
this.activeMaybeSyncResponseCount += 1;
|
|
2052
1969
|
this.activeMaybeSyncResponseCountByPeer.set(
|
|
2053
1970
|
fromHash,
|
|
2054
1971
|
(this.activeMaybeSyncResponseCountByPeer.get(fromHash) ?? 0) + 1,
|
|
2055
1972
|
);
|
|
2056
|
-
|
|
1973
|
+
// Accepted authorizations leave their batch (above) but stay charged
|
|
1974
|
+
// against the global hash budget. Move that charge into row custody so a
|
|
1975
|
+
// disconnect can return it even when the response ship never settles;
|
|
1976
|
+
// units currently owned by finishDelivery (deliveryInFlight) stay out.
|
|
1977
|
+
for (const { authorizations } of acceptedByLifecycle.values()) {
|
|
1978
|
+
for (const authorization of authorizations) {
|
|
1979
|
+
authorization.slotRow = slotRow;
|
|
1980
|
+
if (authorization.deliveryInFlight !== true) {
|
|
1981
|
+
slotRow.pendingResponseHashes += 1;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
let remainingPhysicalLeases = acceptedByLifecycle.size;
|
|
2057
1986
|
return [...acceptedByLifecycle].map(
|
|
2058
1987
|
([targetLifecycle, { hashes: acceptedHashes, authorizations }]) => {
|
|
2059
|
-
let
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
);
|
|
2075
|
-
if (pendingForTarget?.get(authorization.hash) === authorization) {
|
|
2076
|
-
pendingForTarget.delete(authorization.hash);
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
if (pendingForTarget?.size === 0) {
|
|
2080
|
-
this.pendingMaybeSyncResponses.delete(fromHash);
|
|
1988
|
+
let logicalReleased = false;
|
|
1989
|
+
let physicalReleased = false;
|
|
1990
|
+
const releaseLogical = (options?: { fulfilled?: boolean }) => {
|
|
1991
|
+
if (logicalReleased) {
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
logicalReleased = true;
|
|
1995
|
+
slotRow.activeReleases.delete(releaseLogical);
|
|
1996
|
+
const pendingForTarget = this.pendingMaybeSyncResponses.get(fromHash);
|
|
1997
|
+
for (const authorization of authorizations) {
|
|
1998
|
+
this.settlePendingMaybeSyncResponseAuthorization(
|
|
1999
|
+
authorization,
|
|
2000
|
+
options?.fulfilled === true,
|
|
2001
|
+
);
|
|
2002
|
+
if (pendingForTarget?.get(authorization.hash) === authorization) {
|
|
2003
|
+
pendingForTarget.delete(authorization.hash);
|
|
2081
2004
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
)
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2005
|
+
}
|
|
2006
|
+
if (pendingForTarget?.size === 0) {
|
|
2007
|
+
this.pendingMaybeSyncResponses.delete(fromHash);
|
|
2008
|
+
}
|
|
2009
|
+
const chargedToRow = authorizations.filter(
|
|
2010
|
+
(authorization) => authorization.deliveryInFlight !== true,
|
|
2011
|
+
).length;
|
|
2012
|
+
if (slotRow.attached) {
|
|
2013
|
+
this.pendingMaybeSyncResponseCount -= chargedToRow;
|
|
2014
|
+
slotRow.pendingResponseHashes -= chargedToRow;
|
|
2015
|
+
}
|
|
2016
|
+
// else: the hash budget already returned in aggregate when the row
|
|
2017
|
+
// detached; only the deliveryInFlight units (owned by
|
|
2018
|
+
// finishDelivery) remain charged.
|
|
2019
|
+
targetLifecycle.responseLeases -= 1;
|
|
2020
|
+
targetLifecycle.lifecycle.retainedWork -= 1;
|
|
2021
|
+
this.schedulePendingMaybeSyncResponseWaiter();
|
|
2022
|
+
this.maybeDisposeSyncDispatchLifecycle(targetLifecycle.lifecycle);
|
|
2023
|
+
};
|
|
2024
|
+
const releasePhysical = () => {
|
|
2025
|
+
if (physicalReleased) {
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
physicalReleased = true;
|
|
2029
|
+
remainingPhysicalLeases -= 1;
|
|
2030
|
+
if (remainingPhysicalLeases === 0) {
|
|
2031
|
+
slotRow.active -= 1;
|
|
2032
|
+
this.activeMaybeSyncResponseCount -= 1;
|
|
2033
|
+
if (slotRow.attached) {
|
|
2090
2034
|
const activeForPeer =
|
|
2091
2035
|
(this.activeMaybeSyncResponseCountByPeer.get(fromHash) ?? 1) -
|
|
2092
2036
|
1;
|
|
@@ -2098,9 +2042,20 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
2098
2042
|
activeForPeer,
|
|
2099
2043
|
);
|
|
2100
2044
|
}
|
|
2045
|
+
this.maybeDropIdleSyncResponseSlotRow(slotRow);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
};
|
|
2049
|
+
slotRow.activeReleases.add(releaseLogical);
|
|
2050
|
+
return {
|
|
2051
|
+
hashes: acceptedHashes,
|
|
2052
|
+
signal: targetLifecycle.controller.signal,
|
|
2053
|
+
release: (options?: { fulfilled?: boolean }) => {
|
|
2054
|
+
try {
|
|
2055
|
+
releaseLogical(options);
|
|
2056
|
+
} finally {
|
|
2057
|
+
releasePhysical();
|
|
2101
2058
|
}
|
|
2102
|
-
this.schedulePendingMaybeSyncResponseWaiter();
|
|
2103
|
-
this.maybeDisposeSyncDispatchLifecycle(targetLifecycle.lifecycle);
|
|
2104
2059
|
},
|
|
2105
2060
|
};
|
|
2106
2061
|
},
|
|
@@ -2739,11 +2694,26 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
2739
2694
|
return true;
|
|
2740
2695
|
}
|
|
2741
2696
|
const target = from.hashcode();
|
|
2742
|
-
const
|
|
2743
|
-
if (!
|
|
2697
|
+
const lookupPermit = this.tryAcquireCoordinateLookup(target);
|
|
2698
|
+
if (!lookupPermit) {
|
|
2744
2699
|
return true;
|
|
2745
2700
|
}
|
|
2701
|
+
const { release: releaseLookup, row: slotRow } = lookupPermit;
|
|
2746
2702
|
const lifecycle = this.captureSyncDispatchLifecycle([target]);
|
|
2703
|
+
let lifecycleFinished = false;
|
|
2704
|
+
const finishLifecycle = () => {
|
|
2705
|
+
if (lifecycleFinished) {
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
lifecycleFinished = true;
|
|
2709
|
+
slotRow.activeReleases.delete(finishLifecycle);
|
|
2710
|
+
this.finishSyncDispatchLifecycle(lifecycle);
|
|
2711
|
+
this.maybeDropIdleSyncResponseSlotRow(slotRow);
|
|
2712
|
+
};
|
|
2713
|
+
// Dispatch completion is logical ownership. A disconnected peer must
|
|
2714
|
+
// not retain this lifecycle/listeners while a non-abortable lookup or
|
|
2715
|
+
// shipment keeps only its global physical permit alive.
|
|
2716
|
+
slotRow.activeReleases.add(finishLifecycle);
|
|
2747
2717
|
let lookupReleased = false;
|
|
2748
2718
|
const finishLookup = () => {
|
|
2749
2719
|
if (lookupReleased) {
|
|
@@ -2815,7 +2785,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
2815
2785
|
return true;
|
|
2816
2786
|
} finally {
|
|
2817
2787
|
finishLookup();
|
|
2818
|
-
|
|
2788
|
+
finishLifecycle();
|
|
2819
2789
|
}
|
|
2820
2790
|
} else {
|
|
2821
2791
|
return false; // no message was consumed
|
|
@@ -2844,194 +2814,17 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
2844
2814
|
}
|
|
2845
2815
|
|
|
2846
2816
|
private getPendingSyncKeyIdentity(key: SyncableKey): SyncableKey {
|
|
2847
|
-
|
|
2848
|
-
return key;
|
|
2849
|
-
}
|
|
2850
|
-
const hash = this.coordinateToHash.get(key);
|
|
2851
|
-
return hash ?? key;
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
|
-
private removeQueuedSyncCoordinateAlias(key: bigint): void {
|
|
2855
|
-
this.syncInFlightQueuedCoordinates.delete(key);
|
|
2856
|
-
if (this.syncInFlightQueuedCoordinates.size === 0) {
|
|
2857
|
-
this.syncInFlightQueuedCoordinateRefreshIterator = undefined;
|
|
2858
|
-
}
|
|
2859
|
-
const previousHash = this.syncInFlightQueuedHashByCoordinate.get(key);
|
|
2860
|
-
this.syncInFlightQueuedHashByCoordinate.delete(key);
|
|
2861
|
-
if (previousHash != null) {
|
|
2862
|
-
const coordinates =
|
|
2863
|
-
this.syncInFlightQueuedCoordinatesByHash.get(previousHash);
|
|
2864
|
-
coordinates?.delete(key);
|
|
2865
|
-
if (coordinates?.size === 0) {
|
|
2866
|
-
this.syncInFlightQueuedCoordinatesByHash.delete(previousHash);
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
private refreshQueuedSyncCoordinateAlias(key: bigint): void {
|
|
2872
|
-
if (!this.syncInFlightQueue.has(key)) {
|
|
2873
|
-
this.removeQueuedSyncCoordinateAlias(key);
|
|
2874
|
-
return;
|
|
2875
|
-
}
|
|
2876
|
-
this.syncInFlightQueuedCoordinates.add(key);
|
|
2877
|
-
const hash = this.coordinateToHash.get(key) ?? undefined;
|
|
2878
|
-
const previousHash = this.syncInFlightQueuedHashByCoordinate.get(key);
|
|
2879
|
-
if (previousHash !== hash) {
|
|
2880
|
-
if (previousHash != null) {
|
|
2881
|
-
const coordinates =
|
|
2882
|
-
this.syncInFlightQueuedCoordinatesByHash.get(previousHash);
|
|
2883
|
-
coordinates?.delete(key);
|
|
2884
|
-
if (coordinates?.size === 0) {
|
|
2885
|
-
this.syncInFlightQueuedCoordinatesByHash.delete(previousHash);
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
if (hash == null) {
|
|
2889
|
-
this.syncInFlightQueuedHashByCoordinate.delete(key);
|
|
2890
|
-
} else {
|
|
2891
|
-
this.syncInFlightQueuedHashByCoordinate.set(key, hash);
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
if (hash != null) {
|
|
2895
|
-
let coordinates = this.syncInFlightQueuedCoordinatesByHash.get(hash);
|
|
2896
|
-
if (!coordinates) {
|
|
2897
|
-
coordinates = new Set();
|
|
2898
|
-
this.syncInFlightQueuedCoordinatesByHash.set(hash, coordinates);
|
|
2899
|
-
}
|
|
2900
|
-
coordinates.add(key);
|
|
2901
|
-
this.reconcileQueuedSyncCoordinateAlias(key, hash);
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
|
-
private reconcileQueuedSyncCoordinateAlias(
|
|
2906
|
-
coordinate: bigint,
|
|
2907
|
-
hash: string,
|
|
2908
|
-
): void {
|
|
2909
|
-
const now = Date.now();
|
|
2910
|
-
const coordinateExpiresAt = this.syncInFlightQueueExpiresAt.get(coordinate);
|
|
2911
|
-
if (coordinateExpiresAt != null && coordinateExpiresAt <= now) {
|
|
2912
|
-
this.clearSyncProcessKey(coordinate);
|
|
2913
|
-
return;
|
|
2914
|
-
}
|
|
2915
|
-
const hashExpiresAt = this.syncInFlightQueueExpiresAt.get(hash);
|
|
2916
|
-
if (hashExpiresAt != null && hashExpiresAt <= now) {
|
|
2917
|
-
this.clearSyncProcessKey(hash);
|
|
2918
|
-
return;
|
|
2919
|
-
}
|
|
2920
|
-
const hashClaimants = this.syncInFlightQueue.get(hash);
|
|
2921
|
-
if (!hashClaimants || !this.syncInFlightQueue.has(coordinate)) {
|
|
2922
|
-
return;
|
|
2923
|
-
}
|
|
2924
|
-
const expiresAt = Math.min(
|
|
2925
|
-
this.syncInFlightQueueExpiresAt.get(coordinate) ?? Infinity,
|
|
2926
|
-
this.syncInFlightQueueExpiresAt.get(hash) ?? Infinity,
|
|
2927
|
-
);
|
|
2928
|
-
for (const claimant of [...hashClaimants]) {
|
|
2929
|
-
this.addPendingSyncClaim(coordinate, claimant, expiresAt);
|
|
2930
|
-
}
|
|
2931
|
-
if (Number.isFinite(expiresAt)) {
|
|
2932
|
-
this.movePendingSyncKeyExpiryEarlier(coordinate, expiresAt);
|
|
2933
|
-
}
|
|
2934
|
-
for (const target of [...(this.syncInFlightTargetsByKey.get(hash) ?? [])]) {
|
|
2935
|
-
const state = this.syncInFlight.get(target)?.get(hash);
|
|
2936
|
-
if (state) {
|
|
2937
|
-
this.setSyncInFlightTargetKey(target, coordinate, state.timestamp);
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
this.clearSyncProcessKey(hash);
|
|
2817
|
+
return this.pendingSync.getPendingSyncKeyIdentity(key);
|
|
2941
2818
|
}
|
|
2942
2819
|
|
|
2943
2820
|
private refreshQueuedSyncCoordinateAliases(): void {
|
|
2944
|
-
|
|
2945
|
-
this.syncInFlightQueuedCoordinates.size === 0 &&
|
|
2946
|
-
this.syncInFlightQueueClaimants.size < this.syncInFlightQueue.size
|
|
2947
|
-
) {
|
|
2948
|
-
// Defensive compatibility for callers/tests that seed the public queue
|
|
2949
|
-
// directly. Keep hydration bounded; internal writes register coordinates
|
|
2950
|
-
// when the key is first admitted.
|
|
2951
|
-
let inspected = 0;
|
|
2952
|
-
for (const key of this.syncInFlightQueue.keys()) {
|
|
2953
|
-
if (typeof key === "bigint") {
|
|
2954
|
-
this.syncInFlightQueuedCoordinates.add(key);
|
|
2955
|
-
}
|
|
2956
|
-
inspected += 1;
|
|
2957
|
-
if (inspected >= MAX_PENDING_SIMPLE_SYNC_ALIAS_REFRESH_PER_MESSAGE) {
|
|
2958
|
-
break;
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
if (this.syncInFlightQueuedCoordinates.size === 0) {
|
|
2963
|
-
this.syncInFlightQueuedCoordinateRefreshIterator = undefined;
|
|
2964
|
-
return;
|
|
2965
|
-
}
|
|
2966
|
-
this.syncInFlightQueuedCoordinateRefreshIterator ??=
|
|
2967
|
-
this.syncInFlightQueuedCoordinates.values();
|
|
2968
|
-
for (
|
|
2969
|
-
let refreshed = 0;
|
|
2970
|
-
refreshed < MAX_PENDING_SIMPLE_SYNC_ALIAS_REFRESH_PER_MESSAGE;
|
|
2971
|
-
refreshed += 1
|
|
2972
|
-
) {
|
|
2973
|
-
const next = this.syncInFlightQueuedCoordinateRefreshIterator.next();
|
|
2974
|
-
if (next.done) {
|
|
2975
|
-
this.syncInFlightQueuedCoordinateRefreshIterator = undefined;
|
|
2976
|
-
break;
|
|
2977
|
-
}
|
|
2978
|
-
this.refreshQueuedSyncCoordinateAlias(next.value);
|
|
2979
|
-
}
|
|
2821
|
+
this.pendingSync.refreshQueuedSyncCoordinateAliases();
|
|
2980
2822
|
}
|
|
2981
2823
|
|
|
2982
2824
|
private getQueuedSyncKeyForAdmission(
|
|
2983
2825
|
key: SyncableKey,
|
|
2984
2826
|
): SyncableKey | typeof QUEUED_SYNC_ALIAS_REFRESH_PENDING | undefined {
|
|
2985
|
-
|
|
2986
|
-
candidate: SyncableKey,
|
|
2987
|
-
): SyncableKey | undefined => {
|
|
2988
|
-
if (!this.syncInFlightQueue.has(candidate)) {
|
|
2989
|
-
return undefined;
|
|
2990
|
-
}
|
|
2991
|
-
const expiresAt = this.syncInFlightQueueExpiresAt.get(candidate);
|
|
2992
|
-
if (expiresAt != null && expiresAt <= Date.now()) {
|
|
2993
|
-
this.clearSyncProcessKey(candidate);
|
|
2994
|
-
return undefined;
|
|
2995
|
-
}
|
|
2996
|
-
return candidate;
|
|
2997
|
-
};
|
|
2998
|
-
if (getValidQueuedKey(key) != null) {
|
|
2999
|
-
if (typeof key === "bigint") {
|
|
3000
|
-
this.refreshQueuedSyncCoordinateAlias(key);
|
|
3001
|
-
return getValidQueuedKey(key);
|
|
3002
|
-
}
|
|
3003
|
-
return key;
|
|
3004
|
-
}
|
|
3005
|
-
if (typeof key === "string") {
|
|
3006
|
-
const aliases = this.syncInFlightQueuedCoordinatesByHash.get(key);
|
|
3007
|
-
if (aliases) {
|
|
3008
|
-
let inspected = 0;
|
|
3009
|
-
for (const alias of aliases) {
|
|
3010
|
-
if (inspected >= MAX_PENDING_SIMPLE_SYNC_ALIAS_REFRESH_PER_MESSAGE) {
|
|
3011
|
-
return QUEUED_SYNC_ALIAS_REFRESH_PENDING;
|
|
3012
|
-
}
|
|
3013
|
-
inspected += 1;
|
|
3014
|
-
this.refreshQueuedSyncCoordinateAlias(alias);
|
|
3015
|
-
if (
|
|
3016
|
-
this.syncInFlightQueuedCoordinatesByHash.get(key)?.has(alias) ===
|
|
3017
|
-
true
|
|
3018
|
-
) {
|
|
3019
|
-
const validAlias = getValidQueuedKey(alias);
|
|
3020
|
-
if (validAlias != null) {
|
|
3021
|
-
return validAlias;
|
|
3022
|
-
}
|
|
3023
|
-
}
|
|
3024
|
-
}
|
|
3025
|
-
if (
|
|
3026
|
-
(this.syncInFlightQueuedCoordinatesByHash.get(key)?.size ?? 0) > 0
|
|
3027
|
-
) {
|
|
3028
|
-
return QUEUED_SYNC_ALIAS_REFRESH_PENDING;
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
return undefined;
|
|
3032
|
-
}
|
|
3033
|
-
const hash = this.coordinateToHash.get(key);
|
|
3034
|
-
return hash != null ? getValidQueuedKey(hash) : undefined;
|
|
2827
|
+
return this.pendingSync.getQueuedSyncKeyForAdmission(key);
|
|
3035
2828
|
}
|
|
3036
2829
|
|
|
3037
2830
|
private addPendingSyncClaim(
|
|
@@ -3039,80 +2832,11 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3039
2832
|
from: PublicSignKey,
|
|
3040
2833
|
expiresAt?: number,
|
|
3041
2834
|
): boolean {
|
|
3042
|
-
|
|
3043
|
-
let peers = this.syncInFlightQueue.get(key);
|
|
3044
|
-
let claimants = this.syncInFlightQueueClaimants.get(key);
|
|
3045
|
-
let claimantIndexes = this.syncInFlightQueueClaimantIndexes.get(key);
|
|
3046
|
-
if (!peers) {
|
|
3047
|
-
peers = [];
|
|
3048
|
-
this.syncInFlightQueue.set(key, peers);
|
|
3049
|
-
claimants = new Set();
|
|
3050
|
-
this.syncInFlightQueueClaimants.set(key, claimants);
|
|
3051
|
-
claimantIndexes = new Map();
|
|
3052
|
-
this.syncInFlightQueueClaimantIndexes.set(key, claimantIndexes);
|
|
3053
|
-
const deadline = expiresAt ?? Date.now() + PENDING_SIMPLE_SYNC_KEY_TTL_MS;
|
|
3054
|
-
this.syncInFlightQueueExpiresAt.set(key, deadline);
|
|
3055
|
-
const expiryNode: PendingSyncExpiryNode = {
|
|
3056
|
-
kind: "key",
|
|
3057
|
-
key,
|
|
3058
|
-
expiresAt: deadline,
|
|
3059
|
-
heapIndex: -1,
|
|
3060
|
-
};
|
|
3061
|
-
this.pendingSyncKeyExpiryNodes.set(key, expiryNode);
|
|
3062
|
-
this.pushPendingSyncExpiry(expiryNode);
|
|
3063
|
-
if (typeof key === "bigint") {
|
|
3064
|
-
this.refreshQueuedSyncCoordinateAlias(key);
|
|
3065
|
-
}
|
|
3066
|
-
} else if (!claimants) {
|
|
3067
|
-
// Defensive compatibility for callers/tests that seed the public queue
|
|
3068
|
-
// maps directly. Internally every retained key gets this set at creation.
|
|
3069
|
-
claimants = new Set(peers.map((peer) => peer.hashcode()));
|
|
3070
|
-
this.syncInFlightQueueClaimants.set(key, claimants);
|
|
3071
|
-
this.pendingSyncClaimCount += claimants.size;
|
|
3072
|
-
}
|
|
3073
|
-
if (!claimantIndexes) {
|
|
3074
|
-
claimantIndexes = new Map();
|
|
3075
|
-
for (let index = 0; index < peers.length; index += 1) {
|
|
3076
|
-
claimantIndexes.set(peers[index]!.hashcode(), index);
|
|
3077
|
-
}
|
|
3078
|
-
this.syncInFlightQueueClaimantIndexes.set(key, claimantIndexes);
|
|
3079
|
-
}
|
|
3080
|
-
if (claimants.has(fromHash)) {
|
|
3081
|
-
return false;
|
|
3082
|
-
}
|
|
3083
|
-
|
|
3084
|
-
claimantIndexes.set(fromHash, peers.length);
|
|
3085
|
-
peers.push(from);
|
|
3086
|
-
claimants.add(fromHash);
|
|
3087
|
-
let inverted = this.syncInFlightQueueInverted.get(fromHash);
|
|
3088
|
-
if (!inverted) {
|
|
3089
|
-
inverted = new Set();
|
|
3090
|
-
this.syncInFlightQueueInverted.set(fromHash, inverted);
|
|
3091
|
-
}
|
|
3092
|
-
inverted.add(key);
|
|
3093
|
-
this.pendingSyncClaimCount += 1;
|
|
3094
|
-
this.schedulePendingSyncKeyExpiry();
|
|
3095
|
-
return true;
|
|
2835
|
+
return this.pendingSync.addPendingSyncClaim(key, from, expiresAt);
|
|
3096
2836
|
}
|
|
3097
2837
|
|
|
3098
2838
|
private hasPendingSyncClaim(key: SyncableKey, peer: string): boolean {
|
|
3099
|
-
|
|
3100
|
-
if (claimants) {
|
|
3101
|
-
return claimants.has(peer);
|
|
3102
|
-
}
|
|
3103
|
-
const peers = this.syncInFlightQueue.get(key);
|
|
3104
|
-
if (!peers) {
|
|
3105
|
-
return false;
|
|
3106
|
-
}
|
|
3107
|
-
const hydrated = new Set(peers.map((candidate) => candidate.hashcode()));
|
|
3108
|
-
this.syncInFlightQueueClaimants.set(key, hydrated);
|
|
3109
|
-
const indexes = new Map<string, number>();
|
|
3110
|
-
for (let index = 0; index < peers.length; index += 1) {
|
|
3111
|
-
indexes.set(peers[index]!.hashcode(), index);
|
|
3112
|
-
}
|
|
3113
|
-
this.syncInFlightQueueClaimantIndexes.set(key, indexes);
|
|
3114
|
-
this.pendingSyncClaimCount += hydrated.size;
|
|
3115
|
-
return hydrated.has(peer);
|
|
2839
|
+
return this.pendingSync.hasPendingSyncClaim(key, peer);
|
|
3116
2840
|
}
|
|
3117
2841
|
|
|
3118
2842
|
private filterDispatchablePendingSyncClaims(
|
|
@@ -3120,54 +2844,87 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3120
2844
|
peer: string,
|
|
3121
2845
|
epoch: SyncDispatchTargetEpoch,
|
|
3122
2846
|
): SyncableKey[] {
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
for (const key of keys) {
|
|
3129
|
-
const expiresAt = this.syncInFlightQueueExpiresAt.get(key);
|
|
3130
|
-
if (expiresAt != null && expiresAt <= now) {
|
|
3131
|
-
this.clearSyncProcessKey(key);
|
|
3132
|
-
continue;
|
|
3133
|
-
}
|
|
3134
|
-
if (
|
|
3135
|
-
this.syncInFlightQueue.has(key) &&
|
|
3136
|
-
this.hasPendingSyncClaim(key, peer)
|
|
3137
|
-
) {
|
|
3138
|
-
dispatchable.push(key);
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
return dispatchable;
|
|
2847
|
+
return this.pendingSync.filterDispatchablePendingSyncClaims(
|
|
2848
|
+
keys,
|
|
2849
|
+
peer,
|
|
2850
|
+
epoch,
|
|
2851
|
+
);
|
|
3142
2852
|
}
|
|
3143
2853
|
|
|
3144
2854
|
private canStartPendingSyncLookup(peer: string): boolean {
|
|
3145
2855
|
return (
|
|
3146
|
-
this.pendingSyncAdmissionReservations.size +
|
|
2856
|
+
this.pendingSync.pendingSyncAdmissionReservations.size +
|
|
3147
2857
|
this.pendingCoordinateLookupCount <
|
|
3148
2858
|
MAX_PENDING_SIMPLE_SYNC_LOOKUPS_GLOBAL &&
|
|
3149
|
-
(this.pendingSyncAdmissionReservationsByPeer.get(peer)
|
|
2859
|
+
(this.pendingSync.pendingSyncAdmissionReservationsByPeer.get(peer)
|
|
2860
|
+
?.size ?? 0) +
|
|
3150
2861
|
(this.pendingCoordinateLookupCountByPeer.get(peer) ?? 0) <
|
|
3151
2862
|
MAX_PENDING_SIMPLE_SYNC_LOOKUPS_PER_PEER
|
|
3152
2863
|
);
|
|
3153
2864
|
}
|
|
3154
2865
|
|
|
3155
|
-
private
|
|
2866
|
+
private getOrCreateSyncResponseSlotRow(peer: string): SyncPeerSlotRow {
|
|
2867
|
+
return this.peerSlotRows.ensure(peer);
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
private detachSyncResponseSlotRow(peer: string): void {
|
|
2871
|
+
const row = this.peerSlotRows.detach(peer);
|
|
2872
|
+
if (!row) {
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
// Disconnect returns only the current peer generation's quota. The
|
|
2876
|
+
// global counters represent physically live, potentially non-abortable
|
|
2877
|
+
// work and remain charged until each captured release actually settles.
|
|
2878
|
+
this.pendingCoordinateLookupCountByPeer.delete(peer);
|
|
2879
|
+
this.pendingCoordinateResponseCountByPeer.delete(peer);
|
|
2880
|
+
this.activeMaybeSyncResponseCountByPeer.delete(peer);
|
|
2881
|
+
if (row.pendingResponseHashes > 0) {
|
|
2882
|
+
// Active authorizations' hash budget returns in aggregate with the
|
|
2883
|
+
// row. The settled releases below (and any late ship-side release)
|
|
2884
|
+
// see the detached row and skip the global budget.
|
|
2885
|
+
this.pendingMaybeSyncResponseCount -= row.pendingResponseHashes;
|
|
2886
|
+
row.pendingResponseHashes = 0;
|
|
2887
|
+
this.schedulePendingMaybeSyncResponseWaiter();
|
|
2888
|
+
}
|
|
2889
|
+
// Settle only logical active-response ownership so authorizations leave
|
|
2890
|
+
// pendingMaybeSyncResponses and responseLeases/retainedWork drain. The
|
|
2891
|
+
// eventual ship-side release still returns its global physical permit.
|
|
2892
|
+
// Closures capture THIS row (identity, not peer hash), so a reconnected
|
|
2893
|
+
// successor's row is never touched.
|
|
2894
|
+
for (const release of [...row.activeReleases]) {
|
|
2895
|
+
release();
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
private maybeDropIdleSyncResponseSlotRow(row: SyncPeerSlotRow): void {
|
|
2900
|
+
this.peerSlotRows.maybeDropIdle(row);
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
private tryAcquireCoordinateLookup(
|
|
2904
|
+
peer: string,
|
|
2905
|
+
): { row: SyncPeerSlotRow; release: () => void } | undefined {
|
|
3156
2906
|
if (!this.canStartPendingSyncLookup(peer)) {
|
|
3157
2907
|
return undefined;
|
|
3158
2908
|
}
|
|
2909
|
+
const row = this.getOrCreateSyncResponseSlotRow(peer);
|
|
2910
|
+
row.lookups += 1;
|
|
3159
2911
|
this.pendingCoordinateLookupCount += 1;
|
|
3160
2912
|
this.pendingCoordinateLookupCountByPeer.set(
|
|
3161
2913
|
peer,
|
|
3162
2914
|
(this.pendingCoordinateLookupCountByPeer.get(peer) ?? 0) + 1,
|
|
3163
2915
|
);
|
|
3164
2916
|
let released = false;
|
|
3165
|
-
|
|
2917
|
+
const release = () => {
|
|
3166
2918
|
if (released) {
|
|
3167
2919
|
return;
|
|
3168
2920
|
}
|
|
3169
2921
|
released = true;
|
|
2922
|
+
row.lookups -= 1;
|
|
3170
2923
|
this.pendingCoordinateLookupCount -= 1;
|
|
2924
|
+
if (!row.attached) {
|
|
2925
|
+
// Per-peer quota returned when this generation detached.
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
3171
2928
|
const remaining =
|
|
3172
2929
|
(this.pendingCoordinateLookupCountByPeer.get(peer) ?? 1) - 1;
|
|
3173
2930
|
if (remaining === 0) {
|
|
@@ -3175,7 +2932,9 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3175
2932
|
} else {
|
|
3176
2933
|
this.pendingCoordinateLookupCountByPeer.set(peer, remaining);
|
|
3177
2934
|
}
|
|
2935
|
+
this.maybeDropIdleSyncResponseSlotRow(row);
|
|
3178
2936
|
};
|
|
2937
|
+
return { row, release };
|
|
3179
2938
|
}
|
|
3180
2939
|
|
|
3181
2940
|
private tryAcquireCoordinateResponse(peer: string): (() => void) | undefined {
|
|
@@ -3187,6 +2946,8 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3187
2946
|
) {
|
|
3188
2947
|
return undefined;
|
|
3189
2948
|
}
|
|
2949
|
+
const row = this.getOrCreateSyncResponseSlotRow(peer);
|
|
2950
|
+
row.responses += 1;
|
|
3190
2951
|
this.pendingCoordinateResponseCount += 1;
|
|
3191
2952
|
this.pendingCoordinateResponseCountByPeer.set(
|
|
3192
2953
|
peer,
|
|
@@ -3198,7 +2959,12 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3198
2959
|
return;
|
|
3199
2960
|
}
|
|
3200
2961
|
released = true;
|
|
2962
|
+
row.responses -= 1;
|
|
3201
2963
|
this.pendingCoordinateResponseCount -= 1;
|
|
2964
|
+
if (!row.attached) {
|
|
2965
|
+
// Per-peer quota returned when this generation detached.
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
3202
2968
|
const remaining =
|
|
3203
2969
|
(this.pendingCoordinateResponseCountByPeer.get(peer) ?? 1) - 1;
|
|
3204
2970
|
if (remaining === 0) {
|
|
@@ -3206,6 +2972,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3206
2972
|
} else {
|
|
3207
2973
|
this.pendingCoordinateResponseCountByPeer.set(peer, remaining);
|
|
3208
2974
|
}
|
|
2975
|
+
this.maybeDropIdleSyncResponseSlotRow(row);
|
|
3209
2976
|
};
|
|
3210
2977
|
}
|
|
3211
2978
|
|
|
@@ -3213,418 +2980,53 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3213
2980
|
peer: string,
|
|
3214
2981
|
identities: SyncableKey[],
|
|
3215
2982
|
): PendingSyncAdmissionReservation | undefined {
|
|
3216
|
-
|
|
3217
|
-
if (count <= 0) {
|
|
3218
|
-
return undefined;
|
|
3219
|
-
}
|
|
3220
|
-
const reservation: PendingSyncAdmissionReservation = {
|
|
3221
|
-
peer,
|
|
3222
|
-
remaining: count,
|
|
3223
|
-
active: true,
|
|
3224
|
-
released: false,
|
|
3225
|
-
expiresAt: Date.now() + PENDING_SIMPLE_SYNC_KEY_TTL_MS,
|
|
3226
|
-
identities: new Set(identities),
|
|
3227
|
-
retainedSettled: 0,
|
|
3228
|
-
};
|
|
3229
|
-
this.pendingSyncAdmissionReservations.add(reservation);
|
|
3230
|
-
let peerReservations =
|
|
3231
|
-
this.pendingSyncAdmissionReservationsByPeer.get(peer);
|
|
3232
|
-
if (!peerReservations) {
|
|
3233
|
-
peerReservations = new Set();
|
|
3234
|
-
this.pendingSyncAdmissionReservationsByPeer.set(peer, peerReservations);
|
|
3235
|
-
}
|
|
3236
|
-
peerReservations.add(reservation);
|
|
3237
|
-
this.pendingSyncActiveAdmissionReservations += 1;
|
|
3238
|
-
this.pendingSyncAdmissionCount += count;
|
|
3239
|
-
this.pendingSyncAdmissionCountByPeer.set(
|
|
3240
|
-
peer,
|
|
3241
|
-
(this.pendingSyncAdmissionCountByPeer.get(peer) ?? 0) + count,
|
|
3242
|
-
);
|
|
3243
|
-
let reservedIdentities =
|
|
3244
|
-
this.pendingSyncAdmissionIdentitiesByPeer.get(peer);
|
|
3245
|
-
if (!reservedIdentities) {
|
|
3246
|
-
reservedIdentities = new Set();
|
|
3247
|
-
this.pendingSyncAdmissionIdentitiesByPeer.set(peer, reservedIdentities);
|
|
3248
|
-
}
|
|
3249
|
-
for (const identity of identities) {
|
|
3250
|
-
reservedIdentities.add(identity);
|
|
3251
|
-
let reservationsForIdentity =
|
|
3252
|
-
this.pendingSyncAdmissionReservationsByIdentity.get(identity);
|
|
3253
|
-
if (!reservationsForIdentity) {
|
|
3254
|
-
reservationsForIdentity = new Set();
|
|
3255
|
-
this.pendingSyncAdmissionReservationsByIdentity.set(
|
|
3256
|
-
identity,
|
|
3257
|
-
reservationsForIdentity,
|
|
3258
|
-
);
|
|
3259
|
-
}
|
|
3260
|
-
reservationsForIdentity.add(reservation);
|
|
3261
|
-
}
|
|
3262
|
-
const expiryNode: PendingSyncExpiryNode = {
|
|
3263
|
-
kind: "admission",
|
|
3264
|
-
reservation,
|
|
3265
|
-
expiresAt: reservation.expiresAt,
|
|
3266
|
-
heapIndex: -1,
|
|
3267
|
-
};
|
|
3268
|
-
this.pendingSyncAdmissionExpiryNodes.set(reservation, expiryNode);
|
|
3269
|
-
this.pushPendingSyncExpiry(expiryNode);
|
|
3270
|
-
this.schedulePendingSyncKeyExpiry();
|
|
3271
|
-
return reservation;
|
|
3272
|
-
}
|
|
3273
|
-
|
|
3274
|
-
private removePendingSyncAdmissionIdentity(
|
|
3275
|
-
reservation: PendingSyncAdmissionReservation,
|
|
3276
|
-
identity: SyncableKey,
|
|
3277
|
-
options?: { retainQuota?: boolean },
|
|
3278
|
-
): boolean {
|
|
3279
|
-
if (!reservation.identities.delete(identity)) {
|
|
3280
|
-
return false;
|
|
3281
|
-
}
|
|
3282
|
-
if (options?.retainQuota === true) {
|
|
3283
|
-
reservation.retainedSettled += 1;
|
|
3284
|
-
} else {
|
|
3285
|
-
reservation.remaining -= 1;
|
|
3286
|
-
this.pendingSyncAdmissionCount -= 1;
|
|
3287
|
-
const peerCount =
|
|
3288
|
-
(this.pendingSyncAdmissionCountByPeer.get(reservation.peer) ?? 0) - 1;
|
|
3289
|
-
if (peerCount === 0) {
|
|
3290
|
-
this.pendingSyncAdmissionCountByPeer.delete(reservation.peer);
|
|
3291
|
-
} else {
|
|
3292
|
-
this.pendingSyncAdmissionCountByPeer.set(reservation.peer, peerCount);
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
const reservedIdentities = this.pendingSyncAdmissionIdentitiesByPeer.get(
|
|
3296
|
-
reservation.peer,
|
|
3297
|
-
);
|
|
3298
|
-
reservedIdentities?.delete(identity);
|
|
3299
|
-
if (reservedIdentities?.size === 0) {
|
|
3300
|
-
this.pendingSyncAdmissionIdentitiesByPeer.delete(reservation.peer);
|
|
3301
|
-
}
|
|
3302
|
-
const reservationsForIdentity =
|
|
3303
|
-
this.pendingSyncAdmissionReservationsByIdentity.get(identity);
|
|
3304
|
-
reservationsForIdentity?.delete(reservation);
|
|
3305
|
-
if (reservationsForIdentity?.size === 0) {
|
|
3306
|
-
this.pendingSyncAdmissionReservationsByIdentity.delete(identity);
|
|
3307
|
-
}
|
|
3308
|
-
return true;
|
|
2983
|
+
return this.pendingSync.reservePendingSyncAdmission(peer, identities);
|
|
3309
2984
|
}
|
|
3310
2985
|
|
|
3311
2986
|
private clearPendingSyncAdmissionIdentity(identity: SyncableKey): void {
|
|
3312
|
-
|
|
3313
|
-
this.pendingSyncAdmissionReservationsByIdentity.get(identity);
|
|
3314
|
-
if (!reservations) {
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
for (const reservation of [...reservations]) {
|
|
3318
|
-
this.removePendingSyncAdmissionIdentity(reservation, identity, {
|
|
3319
|
-
retainQuota: true,
|
|
3320
|
-
});
|
|
3321
|
-
}
|
|
2987
|
+
this.pendingSync.clearPendingSyncAdmissionIdentity(identity);
|
|
3322
2988
|
}
|
|
3323
2989
|
|
|
3324
2990
|
private consumePendingSyncAdmission(
|
|
3325
2991
|
reservation: PendingSyncAdmissionReservation,
|
|
3326
2992
|
identity: SyncableKey,
|
|
3327
2993
|
): "consumed" | "settled" | "invalid" {
|
|
3328
|
-
|
|
3329
|
-
if (reservation.expiresAt <= Date.now()) {
|
|
3330
|
-
this.invalidatePendingSyncAdmission(reservation);
|
|
3331
|
-
}
|
|
3332
|
-
return "invalid";
|
|
3333
|
-
}
|
|
3334
|
-
if (!reservation.identities.has(identity)) {
|
|
3335
|
-
return "settled";
|
|
3336
|
-
}
|
|
3337
|
-
this.removePendingSyncAdmissionIdentity(reservation, identity);
|
|
3338
|
-
if (reservation.remaining === 0) {
|
|
3339
|
-
this.removePendingSyncAdmissionExpiry(reservation);
|
|
3340
|
-
reservation.active = false;
|
|
3341
|
-
reservation.released = true;
|
|
3342
|
-
this.pendingSyncActiveAdmissionReservations -= 1;
|
|
3343
|
-
this.pendingSyncAdmissionReservations.delete(reservation);
|
|
3344
|
-
const peerReservations = this.pendingSyncAdmissionReservationsByPeer.get(
|
|
3345
|
-
reservation.peer,
|
|
3346
|
-
);
|
|
3347
|
-
peerReservations?.delete(reservation);
|
|
3348
|
-
if (peerReservations?.size === 0) {
|
|
3349
|
-
this.pendingSyncAdmissionReservationsByPeer.delete(reservation.peer);
|
|
3350
|
-
}
|
|
3351
|
-
this.clearPendingSyncExpiryTimerIfIdle();
|
|
3352
|
-
}
|
|
3353
|
-
return "consumed";
|
|
2994
|
+
return this.pendingSync.consumePendingSyncAdmission(reservation, identity);
|
|
3354
2995
|
}
|
|
3355
2996
|
|
|
3356
2997
|
private transferPendingSyncAdmissionIdentity(
|
|
3357
2998
|
peer: string,
|
|
3358
2999
|
identity: SyncableKey,
|
|
3359
3000
|
): number | undefined {
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
}
|
|
3365
|
-
for (const reservation of reservations) {
|
|
3366
|
-
if (
|
|
3367
|
-
reservation.peer !== peer ||
|
|
3368
|
-
reservation.released ||
|
|
3369
|
-
reservation.expiresAt <= Date.now() ||
|
|
3370
|
-
!this.removePendingSyncAdmissionIdentity(reservation, identity, {
|
|
3371
|
-
retainQuota: true,
|
|
3372
|
-
})
|
|
3373
|
-
) {
|
|
3374
|
-
continue;
|
|
3375
|
-
}
|
|
3376
|
-
// The original resolver may be non-abortable and still retains its input
|
|
3377
|
-
// arrays. Keep that reservation charged until its queueSync finally
|
|
3378
|
-
// settles; the queued claim is counted separately and can disappear
|
|
3379
|
-
// without returning the resolver's quota early.
|
|
3380
|
-
return reservation.expiresAt;
|
|
3381
|
-
}
|
|
3382
|
-
return undefined;
|
|
3383
|
-
}
|
|
3384
|
-
|
|
3385
|
-
private invalidatePendingSyncAdmission(
|
|
3386
|
-
reservation?: PendingSyncAdmissionReservation,
|
|
3387
|
-
): void {
|
|
3388
|
-
if (!reservation || reservation.released || !reservation.active) {
|
|
3389
|
-
return;
|
|
3390
|
-
}
|
|
3391
|
-
// Expiry/disconnect invalidates late lookup results, but it must not return
|
|
3392
|
-
// the quota slot while the underlying storage/index work is still alive.
|
|
3393
|
-
// Those lookups are not generally abortable; only queueSync's finally block
|
|
3394
|
-
// may release their active-work accounting.
|
|
3395
|
-
this.removePendingSyncAdmissionExpiry(reservation);
|
|
3396
|
-
reservation.active = false;
|
|
3397
|
-
this.pendingSyncActiveAdmissionReservations -= 1;
|
|
3398
|
-
this.clearPendingSyncExpiryTimerIfIdle();
|
|
3001
|
+
return this.pendingSync.transferPendingSyncAdmissionIdentity(
|
|
3002
|
+
peer,
|
|
3003
|
+
identity,
|
|
3004
|
+
);
|
|
3399
3005
|
}
|
|
3400
3006
|
|
|
3401
3007
|
private releasePendingSyncAdmission(
|
|
3402
3008
|
reservation?: PendingSyncAdmissionReservation,
|
|
3403
3009
|
): void {
|
|
3404
|
-
|
|
3405
|
-
return;
|
|
3406
|
-
}
|
|
3407
|
-
this.removePendingSyncAdmissionExpiry(reservation);
|
|
3408
|
-
for (const identity of [...reservation.identities]) {
|
|
3409
|
-
this.removePendingSyncAdmissionIdentity(reservation, identity);
|
|
3410
|
-
}
|
|
3411
|
-
if (reservation.retainedSettled > 0) {
|
|
3412
|
-
const retainedSettled = reservation.retainedSettled;
|
|
3413
|
-
reservation.retainedSettled = 0;
|
|
3414
|
-
reservation.remaining -= retainedSettled;
|
|
3415
|
-
this.pendingSyncAdmissionCount -= retainedSettled;
|
|
3416
|
-
const peerCount =
|
|
3417
|
-
(this.pendingSyncAdmissionCountByPeer.get(reservation.peer) ?? 0) -
|
|
3418
|
-
retainedSettled;
|
|
3419
|
-
if (peerCount === 0) {
|
|
3420
|
-
this.pendingSyncAdmissionCountByPeer.delete(reservation.peer);
|
|
3421
|
-
} else {
|
|
3422
|
-
this.pendingSyncAdmissionCountByPeer.set(reservation.peer, peerCount);
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
if (reservation.active) {
|
|
3426
|
-
this.pendingSyncActiveAdmissionReservations -= 1;
|
|
3427
|
-
}
|
|
3428
|
-
reservation.active = false;
|
|
3429
|
-
reservation.released = true;
|
|
3430
|
-
this.pendingSyncAdmissionReservations.delete(reservation);
|
|
3431
|
-
const peerReservations = this.pendingSyncAdmissionReservationsByPeer.get(
|
|
3432
|
-
reservation.peer,
|
|
3433
|
-
);
|
|
3434
|
-
peerReservations?.delete(reservation);
|
|
3435
|
-
if (peerReservations?.size === 0) {
|
|
3436
|
-
this.pendingSyncAdmissionReservationsByPeer.delete(reservation.peer);
|
|
3437
|
-
}
|
|
3438
|
-
this.clearPendingSyncExpiryTimerIfIdle();
|
|
3010
|
+
this.pendingSync.releasePendingSyncAdmission(reservation);
|
|
3439
3011
|
}
|
|
3440
3012
|
|
|
3441
3013
|
private clearPendingSyncAdmissions(peer?: string): void {
|
|
3442
|
-
|
|
3443
|
-
peer == null
|
|
3444
|
-
? this.pendingSyncAdmissionReservations
|
|
3445
|
-
: this.pendingSyncAdmissionReservationsByPeer.get(peer);
|
|
3446
|
-
if (!reservations) {
|
|
3447
|
-
return;
|
|
3448
|
-
}
|
|
3449
|
-
for (const reservation of [...reservations]) {
|
|
3450
|
-
this.invalidatePendingSyncAdmission(reservation);
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
|
-
|
|
3454
|
-
private swapPendingSyncExpiry(left: number, right: number): void {
|
|
3455
|
-
const leftNode = this.pendingSyncExpiryHeap[left]!;
|
|
3456
|
-
const rightNode = this.pendingSyncExpiryHeap[right]!;
|
|
3457
|
-
this.pendingSyncExpiryHeap[left] = rightNode;
|
|
3458
|
-
this.pendingSyncExpiryHeap[right] = leftNode;
|
|
3459
|
-
rightNode.heapIndex = left;
|
|
3460
|
-
leftNode.heapIndex = right;
|
|
3461
|
-
}
|
|
3462
|
-
|
|
3463
|
-
private pushPendingSyncExpiry(node: PendingSyncExpiryNode): void {
|
|
3464
|
-
node.heapIndex = this.pendingSyncExpiryHeap.length;
|
|
3465
|
-
this.pendingSyncExpiryHeap.push(node);
|
|
3466
|
-
let index = node.heapIndex;
|
|
3467
|
-
while (index > 0) {
|
|
3468
|
-
const parent = Math.floor((index - 1) / 2);
|
|
3469
|
-
if (
|
|
3470
|
-
this.pendingSyncExpiryHeap[parent]!.expiresAt <=
|
|
3471
|
-
this.pendingSyncExpiryHeap[index]!.expiresAt
|
|
3472
|
-
) {
|
|
3473
|
-
break;
|
|
3474
|
-
}
|
|
3475
|
-
this.swapPendingSyncExpiry(parent, index);
|
|
3476
|
-
index = parent;
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
|
-
private removePendingSyncExpiry(node: PendingSyncExpiryNode): void {
|
|
3481
|
-
const index = node.heapIndex;
|
|
3482
|
-
if (
|
|
3483
|
-
index < 0 ||
|
|
3484
|
-
index >= this.pendingSyncExpiryHeap.length ||
|
|
3485
|
-
this.pendingSyncExpiryHeap[index] !== node
|
|
3486
|
-
) {
|
|
3487
|
-
return;
|
|
3488
|
-
}
|
|
3489
|
-
const last = this.pendingSyncExpiryHeap.pop()!;
|
|
3490
|
-
node.heapIndex = -1;
|
|
3491
|
-
if (index >= this.pendingSyncExpiryHeap.length) {
|
|
3492
|
-
return;
|
|
3493
|
-
}
|
|
3494
|
-
this.pendingSyncExpiryHeap[index] = last;
|
|
3495
|
-
last.heapIndex = index;
|
|
3496
|
-
|
|
3497
|
-
let current = index;
|
|
3498
|
-
while (current > 0) {
|
|
3499
|
-
const parent = Math.floor((current - 1) / 2);
|
|
3500
|
-
if (
|
|
3501
|
-
this.pendingSyncExpiryHeap[parent]!.expiresAt <=
|
|
3502
|
-
this.pendingSyncExpiryHeap[current]!.expiresAt
|
|
3503
|
-
) {
|
|
3504
|
-
break;
|
|
3505
|
-
}
|
|
3506
|
-
this.swapPendingSyncExpiry(parent, current);
|
|
3507
|
-
current = parent;
|
|
3508
|
-
}
|
|
3509
|
-
for (;;) {
|
|
3510
|
-
const left = current * 2 + 1;
|
|
3511
|
-
const right = left + 1;
|
|
3512
|
-
let smallest = current;
|
|
3513
|
-
if (
|
|
3514
|
-
left < this.pendingSyncExpiryHeap.length &&
|
|
3515
|
-
this.pendingSyncExpiryHeap[left]!.expiresAt <
|
|
3516
|
-
this.pendingSyncExpiryHeap[smallest]!.expiresAt
|
|
3517
|
-
) {
|
|
3518
|
-
smallest = left;
|
|
3519
|
-
}
|
|
3520
|
-
if (
|
|
3521
|
-
right < this.pendingSyncExpiryHeap.length &&
|
|
3522
|
-
this.pendingSyncExpiryHeap[right]!.expiresAt <
|
|
3523
|
-
this.pendingSyncExpiryHeap[smallest]!.expiresAt
|
|
3524
|
-
) {
|
|
3525
|
-
smallest = right;
|
|
3526
|
-
}
|
|
3527
|
-
if (smallest === current) {
|
|
3528
|
-
break;
|
|
3529
|
-
}
|
|
3530
|
-
this.swapPendingSyncExpiry(current, smallest);
|
|
3531
|
-
current = smallest;
|
|
3532
|
-
}
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
|
-
private removePendingSyncKeyExpiry(key: SyncableKey): void {
|
|
3536
|
-
const node = this.pendingSyncKeyExpiryNodes.get(key);
|
|
3537
|
-
if (!node) {
|
|
3538
|
-
return;
|
|
3539
|
-
}
|
|
3540
|
-
this.pendingSyncKeyExpiryNodes.delete(key);
|
|
3541
|
-
this.removePendingSyncExpiry(node);
|
|
3014
|
+
this.pendingSync.clearPendingSyncAdmissions(peer);
|
|
3542
3015
|
}
|
|
3543
3016
|
|
|
3544
3017
|
private movePendingSyncKeyExpiryEarlier(
|
|
3545
3018
|
key: SyncableKey,
|
|
3546
3019
|
expiresAt: number,
|
|
3547
3020
|
): void {
|
|
3548
|
-
|
|
3549
|
-
if (current == null || current <= expiresAt) {
|
|
3550
|
-
return;
|
|
3551
|
-
}
|
|
3552
|
-
this.removePendingSyncKeyExpiry(key);
|
|
3553
|
-
this.syncInFlightQueueExpiresAt.set(key, expiresAt);
|
|
3554
|
-
const node: PendingSyncExpiryNode = {
|
|
3555
|
-
kind: "key",
|
|
3556
|
-
key,
|
|
3557
|
-
expiresAt,
|
|
3558
|
-
heapIndex: -1,
|
|
3559
|
-
};
|
|
3560
|
-
this.pendingSyncKeyExpiryNodes.set(key, node);
|
|
3561
|
-
this.pushPendingSyncExpiry(node);
|
|
3562
|
-
this.schedulePendingSyncKeyExpiry();
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
private removePendingSyncAdmissionExpiry(
|
|
3566
|
-
reservation: PendingSyncAdmissionReservation,
|
|
3567
|
-
): void {
|
|
3568
|
-
const node = this.pendingSyncAdmissionExpiryNodes.get(reservation);
|
|
3569
|
-
if (!node) {
|
|
3570
|
-
return;
|
|
3571
|
-
}
|
|
3572
|
-
this.pendingSyncAdmissionExpiryNodes.delete(reservation);
|
|
3573
|
-
this.removePendingSyncExpiry(node);
|
|
3021
|
+
this.pendingSync.movePendingSyncKeyExpiryEarlier(key, expiresAt);
|
|
3574
3022
|
}
|
|
3575
3023
|
|
|
3576
3024
|
private expirePendingSyncKeys(now = Date.now()): void {
|
|
3577
|
-
|
|
3578
|
-
const node = this.pendingSyncExpiryHeap[0];
|
|
3579
|
-
if (!node || node.expiresAt > now) {
|
|
3580
|
-
break;
|
|
3581
|
-
}
|
|
3582
|
-
this.removePendingSyncExpiry(node);
|
|
3583
|
-
if (node.kind === "key") {
|
|
3584
|
-
if (this.pendingSyncKeyExpiryNodes.get(node.key) !== node) {
|
|
3585
|
-
continue;
|
|
3586
|
-
}
|
|
3587
|
-
this.pendingSyncKeyExpiryNodes.delete(node.key);
|
|
3588
|
-
this.clearSyncProcessKey(node.key);
|
|
3589
|
-
} else {
|
|
3590
|
-
if (
|
|
3591
|
-
this.pendingSyncAdmissionExpiryNodes.get(node.reservation) !== node
|
|
3592
|
-
) {
|
|
3593
|
-
continue;
|
|
3594
|
-
}
|
|
3595
|
-
this.pendingSyncAdmissionExpiryNodes.delete(node.reservation);
|
|
3596
|
-
this.invalidatePendingSyncAdmission(node.reservation);
|
|
3597
|
-
}
|
|
3598
|
-
}
|
|
3025
|
+
this.pendingSync.expirePendingSyncKeys(now);
|
|
3599
3026
|
}
|
|
3600
3027
|
|
|
3601
3028
|
private clearPendingSyncExpiryTimerIfIdle(): void {
|
|
3602
|
-
|
|
3603
|
-
this.pendingSyncExpiryHeap.length === 0 &&
|
|
3604
|
-
this.syncInFlightQueueExpiryTimer != null
|
|
3605
|
-
) {
|
|
3606
|
-
clearTimeout(this.syncInFlightQueueExpiryTimer);
|
|
3607
|
-
this.syncInFlightQueueExpiryTimer = undefined;
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
|
|
3611
|
-
private schedulePendingSyncKeyExpiry(): void {
|
|
3612
|
-
if (
|
|
3613
|
-
this.syncInFlightQueueExpiryTimer != null ||
|
|
3614
|
-
this.pendingSyncExpiryHeap.length === 0
|
|
3615
|
-
) {
|
|
3616
|
-
return;
|
|
3617
|
-
}
|
|
3618
|
-
const expiresAt = this.pendingSyncExpiryHeap[0]!.expiresAt;
|
|
3619
|
-
this.syncInFlightQueueExpiryTimer = setTimeout(
|
|
3620
|
-
() => {
|
|
3621
|
-
this.syncInFlightQueueExpiryTimer = undefined;
|
|
3622
|
-
this.expirePendingSyncKeys();
|
|
3623
|
-
this.schedulePendingSyncKeyExpiry();
|
|
3624
|
-
},
|
|
3625
|
-
Math.max(0, expiresAt - Date.now()),
|
|
3626
|
-
);
|
|
3627
|
-
this.syncInFlightQueueExpiryTimer.unref?.();
|
|
3029
|
+
this.pendingSync.clearPendingSyncExpiryTimerIfIdle();
|
|
3628
3030
|
}
|
|
3629
3031
|
|
|
3630
3032
|
async queueSync(
|
|
@@ -3649,10 +3051,10 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3649
3051
|
Math.min(
|
|
3650
3052
|
MAX_PENDING_SIMPLE_SYNC_KEYS_PER_PEER -
|
|
3651
3053
|
peerClaimCount -
|
|
3652
|
-
(this.pendingSyncAdmissionCountByPeer.get(fromHash) ?? 0),
|
|
3054
|
+
(this.pendingSync.pendingSyncAdmissionCountByPeer.get(fromHash) ?? 0),
|
|
3653
3055
|
MAX_PENDING_SIMPLE_SYNC_KEYS_GLOBAL -
|
|
3654
|
-
this.pendingSyncClaimCount -
|
|
3655
|
-
this.pendingSyncAdmissionCount,
|
|
3056
|
+
this.pendingSync.pendingSyncClaimCount -
|
|
3057
|
+
this.pendingSync.pendingSyncAdmissionCount,
|
|
3656
3058
|
),
|
|
3657
3059
|
);
|
|
3658
3060
|
const targetEpoch = this.getOrCreateSyncDispatchTargetEpoch(fromHash);
|
|
@@ -3677,7 +3079,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
3677
3079
|
const identitiesToCheck: SyncableKey[] = [];
|
|
3678
3080
|
const seen = new Set<SyncableKey>();
|
|
3679
3081
|
const pendingAdmissionIdentities =
|
|
3680
|
-
this.pendingSyncAdmissionIdentitiesByPeer.get(fromHash);
|
|
3082
|
+
this.pendingSync.pendingSyncAdmissionIdentitiesByPeer.get(fromHash);
|
|
3681
3083
|
// Default senders use 1,024-key messages. Capping examined input at the
|
|
3682
3084
|
// entire per-peer allowance prevents a duplicate-filled oversized vector
|
|
3683
3085
|
// from turning admission itself into unbounded work.
|
|
@@ -4179,7 +3581,8 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4179
3581
|
if (!isOpenLifecycleActive()) {
|
|
4180
3582
|
return;
|
|
4181
3583
|
}
|
|
4182
|
-
const expiresAt =
|
|
3584
|
+
const expiresAt =
|
|
3585
|
+
this.pendingSync.syncInFlightQueueExpiresAt.get(key);
|
|
4183
3586
|
if (expiresAt != null && expiresAt <= Date.now()) {
|
|
4184
3587
|
this.clearSyncProcessKey(key);
|
|
4185
3588
|
continue;
|
|
@@ -4193,7 +3596,8 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4193
3596
|
if (!value) {
|
|
4194
3597
|
continue;
|
|
4195
3598
|
}
|
|
4196
|
-
const currentExpiresAt =
|
|
3599
|
+
const currentExpiresAt =
|
|
3600
|
+
this.pendingSync.syncInFlightQueueExpiresAt.get(key);
|
|
4197
3601
|
if (currentExpiresAt != null && currentExpiresAt <= Date.now()) {
|
|
4198
3602
|
this.clearSyncProcessKey(key);
|
|
4199
3603
|
continue;
|
|
@@ -4206,8 +3610,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4206
3610
|
}
|
|
4207
3611
|
|
|
4208
3612
|
const cursor =
|
|
4209
|
-
|
|
4210
|
-
value.length;
|
|
3613
|
+
this.pendingSync.getRoundRobinCursor(key) % value.length;
|
|
4211
3614
|
const candidate = value[cursor]!;
|
|
4212
3615
|
const publicKeyHash = candidate.hashcode();
|
|
4213
3616
|
const inflightTimestamp = this.syncInFlight
|
|
@@ -4228,7 +3631,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4228
3631
|
}
|
|
4229
3632
|
request.hashes.push(key);
|
|
4230
3633
|
if (value.length > 1) {
|
|
4231
|
-
this.
|
|
3634
|
+
this.pendingSync.setRoundRobinCursor(
|
|
4232
3635
|
key,
|
|
4233
3636
|
(cursor + 1) % value.length,
|
|
4234
3637
|
);
|
|
@@ -4304,28 +3707,9 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4304
3707
|
this.syncDispatchLifecycleController.abort();
|
|
4305
3708
|
this.syncDispatchTargetEpochs.clear();
|
|
4306
3709
|
this.clearPendingSyncAdmissions();
|
|
4307
|
-
this.syncInFlightQueue.clear();
|
|
4308
|
-
this.syncInFlightQueueInverted.clear();
|
|
4309
3710
|
this.syncInFlightRetryIterator = undefined;
|
|
4310
3711
|
this.syncInFlightRetryRemaining = 0;
|
|
4311
|
-
this.
|
|
4312
|
-
this.pendingSyncExpiryHeap.length = 0;
|
|
4313
|
-
this.pendingSyncKeyExpiryNodes.clear();
|
|
4314
|
-
this.pendingSyncAdmissionExpiryNodes.clear();
|
|
4315
|
-
this.syncInFlightQueueClaimants.clear();
|
|
4316
|
-
this.syncInFlightQueueClaimantIndexes.clear();
|
|
4317
|
-
this.syncInFlightQueueRoundRobinCursor.clear();
|
|
4318
|
-
this.syncInFlightQueuedCoordinates.clear();
|
|
4319
|
-
this.syncInFlightQueuedHashByCoordinate.clear();
|
|
4320
|
-
this.syncInFlightQueuedCoordinatesByHash.clear();
|
|
4321
|
-
this.syncInFlightQueuedCoordinateRefreshIterator = undefined;
|
|
4322
|
-
this.pendingSyncClaimCount = 0;
|
|
4323
|
-
if (this.syncInFlightQueueExpiryTimer != null) {
|
|
4324
|
-
clearTimeout(this.syncInFlightQueueExpiryTimer);
|
|
4325
|
-
this.syncInFlightQueueExpiryTimer = undefined;
|
|
4326
|
-
}
|
|
4327
|
-
this.syncInFlight.clear();
|
|
4328
|
-
this.syncInFlightTargetsByKey.clear();
|
|
3712
|
+
this.pendingSync.clearForClose();
|
|
4329
3713
|
this.recentlySentExchangeHeads.clear();
|
|
4330
3714
|
this.clearPendingMaybeSyncResponses();
|
|
4331
3715
|
for (const sessionId of [...this.repairSessions.keys()]) {
|
|
@@ -4376,127 +3760,19 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4376
3760
|
}
|
|
4377
3761
|
|
|
4378
3762
|
private hasSyncProcessState(): boolean {
|
|
4379
|
-
return (
|
|
4380
|
-
this.syncInFlightQueue.size > 0 ||
|
|
4381
|
-
this.syncInFlightQueueInverted.size > 0 ||
|
|
4382
|
-
this.pendingSyncAdmissionCount > 0 ||
|
|
4383
|
-
this.syncInFlight.size > 0
|
|
4384
|
-
);
|
|
3763
|
+
return this.pendingSync.hasSyncProcessState();
|
|
4385
3764
|
}
|
|
4386
3765
|
|
|
4387
3766
|
private clearSyncProcessKey(key: SyncableKey) {
|
|
4388
|
-
|
|
4389
|
-
if (inflight) {
|
|
4390
|
-
for (const peer of inflight) {
|
|
4391
|
-
const map = this.syncInFlightQueueInverted.get(peer.hashcode());
|
|
4392
|
-
if (map) {
|
|
4393
|
-
map.delete(key);
|
|
4394
|
-
if (map.size === 0) {
|
|
4395
|
-
this.syncInFlightQueueInverted.delete(peer.hashcode());
|
|
4396
|
-
}
|
|
4397
|
-
}
|
|
4398
|
-
}
|
|
4399
|
-
|
|
4400
|
-
const trackedClaimants = this.syncInFlightQueueClaimants.get(key);
|
|
4401
|
-
this.pendingSyncClaimCount = Math.max(
|
|
4402
|
-
0,
|
|
4403
|
-
this.pendingSyncClaimCount -
|
|
4404
|
-
(trackedClaimants?.size ?? inflight.length),
|
|
4405
|
-
);
|
|
4406
|
-
this.syncInFlightQueue.delete(key);
|
|
4407
|
-
}
|
|
4408
|
-
this.syncInFlightQueueClaimants.delete(key);
|
|
4409
|
-
this.syncInFlightQueueClaimantIndexes.delete(key);
|
|
4410
|
-
this.syncInFlightQueueRoundRobinCursor.delete(key);
|
|
4411
|
-
if (typeof key === "bigint") {
|
|
4412
|
-
this.removeQueuedSyncCoordinateAlias(key);
|
|
4413
|
-
}
|
|
4414
|
-
this.removePendingSyncKeyExpiry(key);
|
|
4415
|
-
this.syncInFlightQueueExpiresAt.delete(key);
|
|
4416
|
-
this.clearPendingSyncExpiryTimerIfIdle();
|
|
4417
|
-
|
|
4418
|
-
this.clearSyncInFlightKey(key);
|
|
3767
|
+
this.pendingSync.clearSyncProcessKey(key);
|
|
4419
3768
|
}
|
|
4420
3769
|
|
|
4421
3770
|
private removePendingSyncClaim(key: SyncableKey, peer: string): void {
|
|
4422
|
-
|
|
4423
|
-
if (!inflight) {
|
|
4424
|
-
return;
|
|
4425
|
-
}
|
|
4426
|
-
let claimants = this.syncInFlightQueueClaimants.get(key);
|
|
4427
|
-
let claimantIndexes = this.syncInFlightQueueClaimantIndexes.get(key);
|
|
4428
|
-
if (!claimants || !claimantIndexes) {
|
|
4429
|
-
claimants = new Set();
|
|
4430
|
-
claimantIndexes = new Map();
|
|
4431
|
-
for (let index = 0; index < inflight.length; index += 1) {
|
|
4432
|
-
const claimant = inflight[index]!.hashcode();
|
|
4433
|
-
claimants.add(claimant);
|
|
4434
|
-
claimantIndexes.set(claimant, index);
|
|
4435
|
-
}
|
|
4436
|
-
if (!this.syncInFlightQueueClaimants.has(key)) {
|
|
4437
|
-
this.pendingSyncClaimCount += claimants.size;
|
|
4438
|
-
}
|
|
4439
|
-
this.syncInFlightQueueClaimants.set(key, claimants);
|
|
4440
|
-
this.syncInFlightQueueClaimantIndexes.set(key, claimantIndexes);
|
|
4441
|
-
}
|
|
4442
|
-
const index = claimantIndexes.get(peer);
|
|
4443
|
-
if (index == null) {
|
|
4444
|
-
return;
|
|
4445
|
-
}
|
|
4446
|
-
|
|
4447
|
-
const lastIndex = inflight.length - 1;
|
|
4448
|
-
if (index !== lastIndex) {
|
|
4449
|
-
const lastClaimant = inflight[lastIndex]!;
|
|
4450
|
-
const lastClaimantHash = lastClaimant.hashcode();
|
|
4451
|
-
inflight[index] = lastClaimant;
|
|
4452
|
-
claimantIndexes.set(lastClaimantHash, index);
|
|
4453
|
-
}
|
|
4454
|
-
inflight.pop();
|
|
4455
|
-
claimantIndexes.delete(peer);
|
|
4456
|
-
claimants.delete(peer);
|
|
4457
|
-
this.pendingSyncClaimCount = Math.max(0, this.pendingSyncClaimCount - 1);
|
|
4458
|
-
const inverted = this.syncInFlightQueueInverted.get(peer);
|
|
4459
|
-
inverted?.delete(key);
|
|
4460
|
-
if (inverted?.size === 0) {
|
|
4461
|
-
this.syncInFlightQueueInverted.delete(peer);
|
|
4462
|
-
}
|
|
4463
|
-
if (inflight.length > 0) {
|
|
4464
|
-
const cursor = this.syncInFlightQueueRoundRobinCursor.get(key) ?? 0;
|
|
4465
|
-
this.syncInFlightQueueRoundRobinCursor.set(
|
|
4466
|
-
key,
|
|
4467
|
-
cursor === lastIndex
|
|
4468
|
-
? index % inflight.length
|
|
4469
|
-
: cursor % inflight.length,
|
|
4470
|
-
);
|
|
4471
|
-
return;
|
|
4472
|
-
}
|
|
4473
|
-
|
|
4474
|
-
this.syncInFlightQueue.delete(key);
|
|
4475
|
-
this.syncInFlightQueueClaimants.delete(key);
|
|
4476
|
-
this.syncInFlightQueueClaimantIndexes.delete(key);
|
|
4477
|
-
this.syncInFlightQueueRoundRobinCursor.delete(key);
|
|
4478
|
-
if (typeof key === "bigint") {
|
|
4479
|
-
this.removeQueuedSyncCoordinateAlias(key);
|
|
4480
|
-
}
|
|
4481
|
-
this.removePendingSyncKeyExpiry(key);
|
|
4482
|
-
this.syncInFlightQueueExpiresAt.delete(key);
|
|
4483
|
-
this.clearSyncInFlightKey(key);
|
|
4484
|
-
this.clearPendingSyncExpiryTimerIfIdle();
|
|
3771
|
+
this.pendingSync.removePendingSyncClaim(key, peer);
|
|
4485
3772
|
}
|
|
4486
3773
|
|
|
4487
3774
|
private removeSyncInFlightTargetKey(peer: string, key: SyncableKey): void {
|
|
4488
|
-
|
|
4489
|
-
if (!map?.delete(key)) {
|
|
4490
|
-
return;
|
|
4491
|
-
}
|
|
4492
|
-
if (map.size === 0) {
|
|
4493
|
-
this.syncInFlight.delete(peer);
|
|
4494
|
-
}
|
|
4495
|
-
const targets = this.syncInFlightTargetsByKey.get(key);
|
|
4496
|
-
targets?.delete(peer);
|
|
4497
|
-
if (targets?.size === 0) {
|
|
4498
|
-
this.syncInFlightTargetsByKey.delete(key);
|
|
4499
|
-
}
|
|
3775
|
+
this.pendingSync.removeSyncInFlightTargetKey(peer, key);
|
|
4500
3776
|
}
|
|
4501
3777
|
|
|
4502
3778
|
private setSyncInFlightTargetKey(
|
|
@@ -4504,111 +3780,26 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4504
3780
|
key: SyncableKey,
|
|
4505
3781
|
timestamp: number,
|
|
4506
3782
|
): void {
|
|
4507
|
-
|
|
4508
|
-
if (!map) {
|
|
4509
|
-
map = new Map();
|
|
4510
|
-
this.syncInFlight.set(peer, map);
|
|
4511
|
-
}
|
|
4512
|
-
const existing = map.get(key);
|
|
4513
|
-
if (!existing || existing.timestamp < timestamp) {
|
|
4514
|
-
map.set(key, { timestamp });
|
|
4515
|
-
}
|
|
4516
|
-
let targets = this.syncInFlightTargetsByKey.get(key);
|
|
4517
|
-
if (!targets) {
|
|
4518
|
-
targets = new Set();
|
|
4519
|
-
this.syncInFlightTargetsByKey.set(key, targets);
|
|
4520
|
-
}
|
|
4521
|
-
targets.add(peer);
|
|
3783
|
+
this.pendingSync.setSyncInFlightTargetKey(peer, key, timestamp);
|
|
4522
3784
|
}
|
|
4523
3785
|
|
|
4524
3786
|
private clearSyncInFlightTarget(peer: string): void {
|
|
4525
|
-
|
|
4526
|
-
if (!map) {
|
|
4527
|
-
return;
|
|
4528
|
-
}
|
|
4529
|
-
for (const key of map.keys()) {
|
|
4530
|
-
const targets = this.syncInFlightTargetsByKey.get(key);
|
|
4531
|
-
targets?.delete(peer);
|
|
4532
|
-
if (targets?.size === 0) {
|
|
4533
|
-
this.syncInFlightTargetsByKey.delete(key);
|
|
4534
|
-
}
|
|
4535
|
-
}
|
|
4536
|
-
this.syncInFlight.delete(peer);
|
|
4537
|
-
}
|
|
4538
|
-
|
|
4539
|
-
private clearSyncInFlightKey(key: SyncableKey) {
|
|
4540
|
-
const targets = this.syncInFlightTargetsByKey.get(key);
|
|
4541
|
-
if (!targets) {
|
|
4542
|
-
// Defensive compatibility for tests or integrations that seed the public
|
|
4543
|
-
// syncInFlight map directly. Internal writes always populate the index.
|
|
4544
|
-
for (const [peer, map] of this.syncInFlight) {
|
|
4545
|
-
if (map.has(key)) {
|
|
4546
|
-
this.removeSyncInFlightTargetKey(peer, key);
|
|
4547
|
-
}
|
|
4548
|
-
}
|
|
4549
|
-
return;
|
|
4550
|
-
}
|
|
4551
|
-
for (const peer of [...targets]) {
|
|
4552
|
-
this.removeSyncInFlightTargetKey(peer, key);
|
|
4553
|
-
}
|
|
4554
|
-
}
|
|
4555
|
-
|
|
4556
|
-
private forEachKnownAlias(
|
|
4557
|
-
hash: string,
|
|
4558
|
-
callback: (key: SyncableKey) => void,
|
|
4559
|
-
): void {
|
|
4560
|
-
callback(hash);
|
|
4561
|
-
for (const coordinate of [
|
|
4562
|
-
...(this.syncInFlightQueuedCoordinatesByHash.get(hash) ?? []),
|
|
4563
|
-
]) {
|
|
4564
|
-
callback(coordinate);
|
|
4565
|
-
}
|
|
4566
|
-
}
|
|
4567
|
-
|
|
4568
|
-
private clearSyncInFlightForPeer(publicKeyHash: string, hash: string) {
|
|
4569
|
-
const map = this.syncInFlight.get(publicKeyHash);
|
|
4570
|
-
if (!map) {
|
|
4571
|
-
return;
|
|
4572
|
-
}
|
|
4573
|
-
this.refreshQueuedSyncCoordinateAliases();
|
|
4574
|
-
this.forEachKnownAlias(hash, (key) =>
|
|
4575
|
-
this.removeSyncInFlightTargetKey(publicKeyHash, key),
|
|
4576
|
-
);
|
|
3787
|
+
this.pendingSync.clearSyncInFlightTarget(peer);
|
|
4577
3788
|
}
|
|
4578
3789
|
|
|
4579
3790
|
private clearSyncInFlightForPeerHashes(
|
|
4580
3791
|
publicKeyHash: string,
|
|
4581
3792
|
hashes: string[],
|
|
4582
3793
|
) {
|
|
4583
|
-
|
|
4584
|
-
if (!map || hashes.length === 0) {
|
|
4585
|
-
return;
|
|
4586
|
-
}
|
|
4587
|
-
this.refreshQueuedSyncCoordinateAliases();
|
|
4588
|
-
for (const hash of hashes) {
|
|
4589
|
-
this.forEachKnownAlias(hash, (key) =>
|
|
4590
|
-
this.removeSyncInFlightTargetKey(publicKeyHash, key),
|
|
4591
|
-
);
|
|
4592
|
-
}
|
|
3794
|
+
this.pendingSync.clearSyncInFlightForPeerHashes(publicKeyHash, hashes);
|
|
4593
3795
|
}
|
|
4594
3796
|
|
|
4595
3797
|
private clearSyncProcess(hash: string) {
|
|
4596
|
-
this.
|
|
4597
|
-
this.forEachKnownAlias(hash, (key) => this.clearSyncProcessKey(key));
|
|
3798
|
+
this.pendingSync.clearSyncProcess(hash);
|
|
4598
3799
|
}
|
|
4599
3800
|
|
|
4600
3801
|
private clearSyncProcesses(hashes: string[]) {
|
|
4601
|
-
|
|
4602
|
-
return;
|
|
4603
|
-
}
|
|
4604
|
-
this.refreshQueuedSyncCoordinateAliases();
|
|
4605
|
-
const keys = new Set<SyncableKey>();
|
|
4606
|
-
for (const hash of hashes) {
|
|
4607
|
-
this.forEachKnownAlias(hash, (key) => keys.add(key));
|
|
4608
|
-
}
|
|
4609
|
-
for (const key of keys) {
|
|
4610
|
-
this.clearSyncProcessKey(key);
|
|
4611
|
-
}
|
|
3802
|
+
this.pendingSync.clearSyncProcesses(hashes);
|
|
4612
3803
|
}
|
|
4613
3804
|
|
|
4614
3805
|
onPeerDisconnected(key: PublicSignKey | string): Promise<void> | void {
|
|
@@ -4619,7 +3810,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4619
3810
|
this.syncDispatchTargetEpochs.delete(publicKeyHash);
|
|
4620
3811
|
this.clearPendingSyncAdmissions(publicKeyHash);
|
|
4621
3812
|
for (const targetLifecycle of [
|
|
4622
|
-
...(this.
|
|
3813
|
+
...(this.syncDispatchRegistry.activeTargets.get(publicKeyHash) ?? []),
|
|
4623
3814
|
]) {
|
|
4624
3815
|
if (targetLifecycle.lifecycle.abortAllOnTargetDisconnect) {
|
|
4625
3816
|
this.abortSyncDispatchLifecycle(
|
|
@@ -4636,13 +3827,12 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
4636
3827
|
this.clearSyncInFlightTarget(publicKeyHash);
|
|
4637
3828
|
this.recentlySentExchangeHeads.delete(publicKeyHash);
|
|
4638
3829
|
this.clearPendingMaybeSyncResponses(publicKeyHash);
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
}
|
|
3830
|
+
this.pendingSync.removeClaimsForPeer(publicKeyHash);
|
|
3831
|
+
// Storage lookups and response ships for this peer may never settle
|
|
3832
|
+
// (the resolvers are not universally abortable). Detach the slot row so
|
|
3833
|
+
// the peer's quota returns immediately; the captured closures settle
|
|
3834
|
+
// against the detached row without touching a successor's quota.
|
|
3835
|
+
this.detachSyncResponseSlotRow(publicKeyHash);
|
|
4646
3836
|
}
|
|
4647
3837
|
|
|
4648
3838
|
get pending() {
|