@peerbit/shared-log 16.0.30 → 16.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +101 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/sync/dispatch-lifecycle.d.ts +5 -0
- package/dist/src/sync/dispatch-lifecycle.d.ts.map +1 -1
- package/dist/src/sync/dispatch-lifecycle.js +15 -0
- package/dist/src/sync/dispatch-lifecycle.js.map +1 -1
- package/dist/src/sync/index.d.ts +10 -1
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.d.ts +8 -1
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +140 -13
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +6 -2
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +181 -122
- package/dist/src/sync/simple.js.map +1 -1
- package/package.json +12 -12
- package/src/index.ts +128 -6
- package/src/sync/dispatch-lifecycle.ts +20 -0
- package/src/sync/index.ts +10 -0
- package/src/sync/rateless-iblt.ts +178 -11
- package/src/sync/simple.ts +252 -154
package/dist/src/sync/simple.js
CHANGED
|
@@ -39,7 +39,7 @@ import { Entry, Log } from "@peerbit/log";
|
|
|
39
39
|
import { CONVERGENCE_MESSAGE_PRIORITY, SilentDelivery, } from "@peerbit/stream-interface";
|
|
40
40
|
import { EntryWithRefs, createExchangeHeadsMessages, createRawExchangeHeadsMessages, } from "../exchange-heads.js";
|
|
41
41
|
import { TransportMessage } from "../message.js";
|
|
42
|
-
import { DispatchLifecycleRegistry } from "./dispatch-lifecycle.js";
|
|
42
|
+
import { DispatchLifecycleRegistry, isSyncDispatchCancellation, } from "./dispatch-lifecycle.js";
|
|
43
43
|
import { PendingSyncStore, QUEUED_SYNC_ALIAS_REFRESH_PENDING, } from "./pending-sync-store.js";
|
|
44
44
|
import { emitSyncProfileDuration, syncProfileStart } from "./profile.js";
|
|
45
45
|
import { SyncPeerSlotRegistry, } from "./sync-peer-state.js";
|
|
@@ -702,13 +702,14 @@ export class SimpleSyncronizer {
|
|
|
702
702
|
filterRecentlySentExchangeHeads(hashes, peer) {
|
|
703
703
|
const peerHash = peer.hashcode();
|
|
704
704
|
const now = Date.now();
|
|
705
|
+
const stamp = { timestamp: now };
|
|
705
706
|
let recentlySent = this.recentlySentExchangeHeads.get(peerHash);
|
|
706
707
|
if (!recentlySent) {
|
|
707
708
|
recentlySent = new Map();
|
|
708
709
|
this.recentlySentExchangeHeads.set(peerHash, recentlySent);
|
|
709
710
|
}
|
|
710
|
-
for (const [hash,
|
|
711
|
-
if (now - timestamp > EXCHANGE_HEAD_RESPONSE_DEDUPE_TTL_MS) {
|
|
711
|
+
for (const [hash, previous] of recentlySent) {
|
|
712
|
+
if (now - previous.timestamp > EXCHANGE_HEAD_RESPONSE_DEDUPE_TTL_MS) {
|
|
712
713
|
recentlySent.delete(hash);
|
|
713
714
|
}
|
|
714
715
|
}
|
|
@@ -725,22 +726,25 @@ export class SimpleSyncronizer {
|
|
|
725
726
|
if (this.isEntryRecentlyKnownByPeer?.(hash, peerHash, RECENT_KNOWN_EXCHANGE_HEAD_SUPPRESSION_MS)) {
|
|
726
727
|
continue;
|
|
727
728
|
}
|
|
728
|
-
recentlySent.set(hash,
|
|
729
|
+
recentlySent.set(hash, stamp);
|
|
729
730
|
out.push(hash);
|
|
730
731
|
}
|
|
731
|
-
return
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
732
|
+
return {
|
|
733
|
+
hashes: out,
|
|
734
|
+
rollback: () => {
|
|
735
|
+
// A cancelled predecessor may settle after a fresh receive shipped
|
|
736
|
+
// the same hash. Only undo this exact attempt's dedupe ownership.
|
|
737
|
+
for (const hash of out) {
|
|
738
|
+
if (recentlySent.get(hash) === stamp) {
|
|
739
|
+
recentlySent.delete(hash);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (recentlySent.size === 0 &&
|
|
743
|
+
this.recentlySentExchangeHeads.get(peerHash) === recentlySent) {
|
|
744
|
+
this.recentlySentExchangeHeads.delete(peerHash);
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
};
|
|
744
748
|
}
|
|
745
749
|
getOrCreateSyncDispatchTargetEpoch(target) {
|
|
746
750
|
let epoch = this.syncDispatchTargetEpochs.get(target);
|
|
@@ -774,7 +778,10 @@ export class SimpleSyncronizer {
|
|
|
774
778
|
const currentEpoch = this.syncDispatchTargetEpochs.get(target);
|
|
775
779
|
const epoch = expectedEpoch ??
|
|
776
780
|
currentEpoch ??
|
|
777
|
-
(options?.createTargetEpochs === false
|
|
781
|
+
(options?.createTargetEpochs === false ||
|
|
782
|
+
this.closed === true ||
|
|
783
|
+
callerSignal?.aborted === true ||
|
|
784
|
+
ownershipLifecycleController.signal.aborted
|
|
778
785
|
? undefined
|
|
779
786
|
: this.getOrCreateSyncDispatchTargetEpoch(target));
|
|
780
787
|
if (!epoch) {
|
|
@@ -1916,7 +1923,11 @@ export class SimpleSyncronizer {
|
|
|
1916
1923
|
requestedCount: trackedHashes.length,
|
|
1917
1924
|
requestedTotalCount: allHashes.length,
|
|
1918
1925
|
attempts: 0,
|
|
1919
|
-
|
|
1926
|
+
// Rejected late repairs only need result metadata. Do not create a
|
|
1927
|
+
// retained dispatch epoch after the terminal admission fence.
|
|
1928
|
+
targetEpoch: this.closed
|
|
1929
|
+
? { id: 0 }
|
|
1930
|
+
: this.getOrCreateSyncDispatchTargetEpoch(target),
|
|
1920
1931
|
});
|
|
1921
1932
|
}
|
|
1922
1933
|
const session = {
|
|
@@ -1930,6 +1941,10 @@ export class SimpleSyncronizer {
|
|
|
1930
1941
|
deferred,
|
|
1931
1942
|
cancelled: false,
|
|
1932
1943
|
};
|
|
1944
|
+
if (this.closed) {
|
|
1945
|
+
deferred.resolve(this.buildRepairSessionResult(session, false));
|
|
1946
|
+
return { id, done: deferred.promise, cancel: () => { } };
|
|
1947
|
+
}
|
|
1933
1948
|
if (allHashes.length === 0 || targets.length === 0) {
|
|
1934
1949
|
deferred.resolve(this.buildRepairSessionResult(session, true));
|
|
1935
1950
|
return {
|
|
@@ -2033,8 +2048,8 @@ export class SimpleSyncronizer {
|
|
|
2033
2048
|
}
|
|
2034
2049
|
catch (error) {
|
|
2035
2050
|
reservation.release();
|
|
2036
|
-
if (!this.isSyncDispatchLifecycleActive(lifecycle) ||
|
|
2037
|
-
!this.isSyncDispatchLifecycleActive(lifecycle, target)) {
|
|
2051
|
+
if (isSyncDispatchCancellation(error, this.getSyncDispatchSignal(lifecycle, target), !this.isSyncDispatchLifecycleActive(lifecycle) ||
|
|
2052
|
+
!this.isSyncDispatchLifecycleActive(lifecycle, target))) {
|
|
2038
2053
|
break;
|
|
2039
2054
|
}
|
|
2040
2055
|
throw error;
|
|
@@ -2083,7 +2098,7 @@ export class SimpleSyncronizer {
|
|
|
2083
2098
|
sentMessages = await this.sendRawExchangeHeads(hashes, [to.hashcode()], { signal });
|
|
2084
2099
|
}
|
|
2085
2100
|
catch (error) {
|
|
2086
|
-
if (signal
|
|
2101
|
+
if (isSyncDispatchCancellation(error, signal)) {
|
|
2087
2102
|
return { messages: 0, fused: true };
|
|
2088
2103
|
}
|
|
2089
2104
|
throw error;
|
|
@@ -2108,7 +2123,7 @@ export class SimpleSyncronizer {
|
|
|
2108
2123
|
messages += 1;
|
|
2109
2124
|
}
|
|
2110
2125
|
catch (error) {
|
|
2111
|
-
if (signal
|
|
2126
|
+
if (isSyncDispatchCancellation(error, signal)) {
|
|
2112
2127
|
break;
|
|
2113
2128
|
}
|
|
2114
2129
|
throw error;
|
|
@@ -2125,7 +2140,7 @@ export class SimpleSyncronizer {
|
|
|
2125
2140
|
* exact advertised hash set.
|
|
2126
2141
|
*/
|
|
2127
2142
|
async shipAuthorizedMaybeSyncResponse(properties) {
|
|
2128
|
-
const hashes = this.filterRecentlySentExchangeHeads(properties.hashes, properties.from);
|
|
2143
|
+
const { hashes, rollback } = this.filterRecentlySentExchangeHeads(properties.hashes, properties.from);
|
|
2129
2144
|
try {
|
|
2130
2145
|
const shipped = await this.shipExchangeHeads(hashes, properties.from, canReceiveRawExchangeHeads(properties.response), properties.signal);
|
|
2131
2146
|
return {
|
|
@@ -2134,12 +2149,12 @@ export class SimpleSyncronizer {
|
|
|
2134
2149
|
};
|
|
2135
2150
|
}
|
|
2136
2151
|
catch (error) {
|
|
2137
|
-
|
|
2152
|
+
rollback();
|
|
2138
2153
|
throw error;
|
|
2139
2154
|
}
|
|
2140
2155
|
finally {
|
|
2141
2156
|
if (properties.signal.aborted) {
|
|
2142
|
-
|
|
2157
|
+
rollback();
|
|
2143
2158
|
}
|
|
2144
2159
|
}
|
|
2145
2160
|
}
|
|
@@ -2154,18 +2169,21 @@ export class SimpleSyncronizer {
|
|
|
2154
2169
|
let entries = 0;
|
|
2155
2170
|
let firstError;
|
|
2156
2171
|
for (const lease of properties.leases) {
|
|
2172
|
+
const signal = properties.signal
|
|
2173
|
+
? AbortSignal.any([lease.signal, properties.signal])
|
|
2174
|
+
: lease.signal;
|
|
2157
2175
|
let fulfilled = false;
|
|
2158
2176
|
try {
|
|
2159
2177
|
const shipped = await this.shipAuthorizedMaybeSyncResponse({
|
|
2160
2178
|
hashes: lease.hashes,
|
|
2161
2179
|
from: properties.from,
|
|
2162
2180
|
response: properties.response,
|
|
2163
|
-
signal
|
|
2181
|
+
signal,
|
|
2164
2182
|
});
|
|
2165
2183
|
messages += shipped.messages;
|
|
2166
2184
|
fused ||= shipped.fused;
|
|
2167
2185
|
entries += shipped.entries;
|
|
2168
|
-
fulfilled = !
|
|
2186
|
+
fulfilled = !signal.aborted;
|
|
2169
2187
|
}
|
|
2170
2188
|
catch (error) {
|
|
2171
2189
|
firstError ??= error;
|
|
@@ -2191,10 +2209,17 @@ export class SimpleSyncronizer {
|
|
|
2191
2209
|
}
|
|
2192
2210
|
return { messages, fused, entries };
|
|
2193
2211
|
}
|
|
2194
|
-
async onMessage(msg, context) {
|
|
2212
|
+
async onMessage(msg, context, options) {
|
|
2213
|
+
if (options?.signal?.aborted) {
|
|
2214
|
+
return (msg instanceof RequestMaybeSync ||
|
|
2215
|
+
msg instanceof ResponseMaybeSync ||
|
|
2216
|
+
msg instanceof ResponseMaybeSyncCapabilities ||
|
|
2217
|
+
msg instanceof RequestMaybeSyncCoordinate ||
|
|
2218
|
+
msg instanceof RequestMaybeSyncCoordinateCapabilities);
|
|
2219
|
+
}
|
|
2195
2220
|
const from = context.from;
|
|
2196
2221
|
if (msg instanceof RequestMaybeSync) {
|
|
2197
|
-
await this.queueSync(msg.hashes, from);
|
|
2222
|
+
await this.queueSync(msg.hashes, from, options);
|
|
2198
2223
|
return true;
|
|
2199
2224
|
}
|
|
2200
2225
|
else if (msg instanceof ResponseMaybeSync ||
|
|
@@ -2209,6 +2234,7 @@ export class SimpleSyncronizer {
|
|
|
2209
2234
|
leases: pending,
|
|
2210
2235
|
from,
|
|
2211
2236
|
response: msg,
|
|
2237
|
+
signal: options?.signal,
|
|
2212
2238
|
});
|
|
2213
2239
|
return true;
|
|
2214
2240
|
}
|
|
@@ -2224,7 +2250,7 @@ export class SimpleSyncronizer {
|
|
|
2224
2250
|
return true;
|
|
2225
2251
|
}
|
|
2226
2252
|
const { release: releaseLookup, row: slotRow } = lookupPermit;
|
|
2227
|
-
const lifecycle = this.captureSyncDispatchLifecycle([target]);
|
|
2253
|
+
const lifecycle = this.captureSyncDispatchLifecycle([target], options?.signal);
|
|
2228
2254
|
let lifecycleFinished = false;
|
|
2229
2255
|
const finishLifecycle = () => {
|
|
2230
2256
|
if (lifecycleFinished) {
|
|
@@ -2274,12 +2300,22 @@ export class SimpleSyncronizer {
|
|
|
2274
2300
|
let hashesToSend = [];
|
|
2275
2301
|
let messages = 0;
|
|
2276
2302
|
let fused = false;
|
|
2303
|
+
let rollback;
|
|
2304
|
+
const signal = this.getSyncDispatchSignal(lifecycle, target);
|
|
2277
2305
|
try {
|
|
2278
|
-
|
|
2306
|
+
({ hashes: hashesToSend, rollback } =
|
|
2307
|
+
this.filterRecentlySentExchangeHeads(hashes, from));
|
|
2279
2308
|
// dont set priority 1 here because this will block other messages that should higher priority
|
|
2280
|
-
({ messages, fused } = await this.shipExchangeHeads(hashesToSend, context.from, canReceiveRawExchangeHeads(msg),
|
|
2309
|
+
({ messages, fused } = await this.shipExchangeHeads(hashesToSend, context.from, canReceiveRawExchangeHeads(msg), signal));
|
|
2310
|
+
}
|
|
2311
|
+
catch (error) {
|
|
2312
|
+
rollback?.();
|
|
2313
|
+
throw error;
|
|
2281
2314
|
}
|
|
2282
2315
|
finally {
|
|
2316
|
+
if (signal.aborted) {
|
|
2317
|
+
rollback?.();
|
|
2318
|
+
}
|
|
2283
2319
|
releaseResponse();
|
|
2284
2320
|
if (profile) {
|
|
2285
2321
|
emitSyncProfileDuration(profile, exchangeStartedAt, {
|
|
@@ -2468,7 +2504,7 @@ export class SimpleSyncronizer {
|
|
|
2468
2504
|
this.pendingSync.clearPendingSyncExpiryTimerIfIdle();
|
|
2469
2505
|
}
|
|
2470
2506
|
async queueSync(keys, from, options) {
|
|
2471
|
-
if (this.closed === true || keys.length === 0) {
|
|
2507
|
+
if (this.closed === true || options?.signal?.aborted || keys.length === 0) {
|
|
2472
2508
|
return;
|
|
2473
2509
|
}
|
|
2474
2510
|
// A delayed timer must not let expired claims or admission reservations
|
|
@@ -2486,6 +2522,7 @@ export class SimpleSyncronizer {
|
|
|
2486
2522
|
const targetEpoch = this.getOrCreateSyncDispatchTargetEpoch(fromHash);
|
|
2487
2523
|
const ownershipLifecycleController = this.syncDispatchLifecycleController;
|
|
2488
2524
|
const isCapturedLifecycleActive = () => this.closed !== true &&
|
|
2525
|
+
options?.signal?.aborted !== true &&
|
|
2489
2526
|
this.syncDispatchLifecycleController === ownershipLifecycleController &&
|
|
2490
2527
|
!ownershipLifecycleController.signal.aborted &&
|
|
2491
2528
|
this.syncDispatchTargetEpochs.get(fromHash) === targetEpoch;
|
|
@@ -2555,6 +2592,7 @@ export class SimpleSyncronizer {
|
|
|
2555
2592
|
const dispatchableExistingRequestHashes = this.filterDispatchablePendingSyncClaims(existingRequestHashes, fromHash, targetEpoch);
|
|
2556
2593
|
const existingRequest = dispatchableExistingRequestHashes.length > 0
|
|
2557
2594
|
? this.requestSync(dispatchableExistingRequestHashes, [fromHash], {
|
|
2595
|
+
signal: options?.signal,
|
|
2558
2596
|
ownershipLifecycleController,
|
|
2559
2597
|
targetEpochs: new Map([[fromHash, targetEpoch]]),
|
|
2560
2598
|
createTargetEpochs: false,
|
|
@@ -2565,107 +2603,115 @@ export class SimpleSyncronizer {
|
|
|
2565
2603
|
// when a later lifecycle check returns before joining it.
|
|
2566
2604
|
void existingRequest?.catch(() => { });
|
|
2567
2605
|
const resolveKnownStartedAt = syncProfileStart(profile);
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
return;
|
|
2574
|
-
}
|
|
2575
|
-
if (profile) {
|
|
2576
|
-
emitSyncProfileDuration(profile, resolveKnownStartedAt, {
|
|
2577
|
-
name: "simple.queueSync.resolveKnown",
|
|
2578
|
-
entries: keysToCheck.length,
|
|
2579
|
-
count: knownKeys?.keys.size ?? 0,
|
|
2580
|
-
details: {
|
|
2581
|
-
checkedCoordinates: knownKeys?.checkedCoordinates === true,
|
|
2582
|
-
checkedHashes: knownKeys?.checkedHashes === true,
|
|
2583
|
-
skipCheck: options?.skipCheck === true,
|
|
2584
|
-
},
|
|
2585
|
-
});
|
|
2586
|
-
}
|
|
2587
|
-
if (keysToCheck.length > 0) {
|
|
2588
|
-
// A resolver/index lookup may have populated coordinateToHash while
|
|
2589
|
-
// it yielded. Refresh another fixed-size slice, never the full queue.
|
|
2590
|
-
this.refreshQueuedSyncCoordinateAliases();
|
|
2591
|
-
}
|
|
2592
|
-
const loopStartedAt = syncProfileStart(profile);
|
|
2593
|
-
for (let index = 0; index < keysToCheck.length; index += 1) {
|
|
2594
|
-
const key = keysToCheck[index];
|
|
2595
|
-
const identity = identitiesToCheck[index];
|
|
2606
|
+
const planning = (async () => {
|
|
2607
|
+
try {
|
|
2608
|
+
const knownKeys = options?.skipCheck === true || keysToCheck.length === 0
|
|
2609
|
+
? undefined
|
|
2610
|
+
: await this.resolveKnownSyncKeys(keysToCheck);
|
|
2596
2611
|
if (!isCapturedLifecycleActive()) {
|
|
2597
2612
|
return;
|
|
2598
2613
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2614
|
+
if (profile) {
|
|
2615
|
+
emitSyncProfileDuration(profile, resolveKnownStartedAt, {
|
|
2616
|
+
name: "simple.queueSync.resolveKnown",
|
|
2617
|
+
entries: keysToCheck.length,
|
|
2618
|
+
count: knownKeys?.keys.size ?? 0,
|
|
2619
|
+
details: {
|
|
2620
|
+
checkedCoordinates: knownKeys?.checkedCoordinates === true,
|
|
2621
|
+
checkedHashes: knownKeys?.checkedHashes === true,
|
|
2622
|
+
skipCheck: options?.skipCheck === true,
|
|
2623
|
+
},
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
if (keysToCheck.length > 0) {
|
|
2627
|
+
// A resolver/index lookup may have populated coordinateToHash while
|
|
2628
|
+
// it yielded. Refresh another fixed-size slice, never the full queue.
|
|
2629
|
+
this.refreshQueuedSyncCoordinateAliases();
|
|
2630
|
+
}
|
|
2631
|
+
const loopStartedAt = syncProfileStart(profile);
|
|
2632
|
+
for (let index = 0; index < keysToCheck.length; index += 1) {
|
|
2633
|
+
const key = keysToCheck[index];
|
|
2634
|
+
const identity = identitiesToCheck[index];
|
|
2635
|
+
if (!isCapturedLifecycleActive()) {
|
|
2603
2636
|
return;
|
|
2604
2637
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
const coordinateOrHash = queuedKeyResult ?? key;
|
|
2608
|
-
const inFlight = this.syncInFlightQueue.get(coordinateOrHash);
|
|
2609
|
-
if (inFlight) {
|
|
2610
|
-
if (!this.hasPendingSyncClaim(coordinateOrHash, fromHash)) {
|
|
2638
|
+
const queuedKeyResult = this.getQueuedSyncKeyForAdmission(key);
|
|
2639
|
+
if (queuedKeyResult === QUEUED_SYNC_ALIAS_REFRESH_PENDING) {
|
|
2611
2640
|
const consumption = this.consumePendingSyncAdmission(admission, identity);
|
|
2612
2641
|
if (consumption === "invalid") {
|
|
2613
2642
|
return;
|
|
2614
2643
|
}
|
|
2615
|
-
|
|
2644
|
+
continue;
|
|
2645
|
+
}
|
|
2646
|
+
const coordinateOrHash = queuedKeyResult ?? key;
|
|
2647
|
+
const inFlight = this.syncInFlightQueue.get(coordinateOrHash);
|
|
2648
|
+
if (inFlight) {
|
|
2649
|
+
if (!this.hasPendingSyncClaim(coordinateOrHash, fromHash)) {
|
|
2650
|
+
const consumption = this.consumePendingSyncAdmission(admission, identity);
|
|
2651
|
+
if (consumption === "invalid") {
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
if (consumption === "settled") {
|
|
2655
|
+
continue;
|
|
2656
|
+
}
|
|
2657
|
+
this.movePendingSyncKeyExpiryEarlier(coordinateOrHash, admission.expiresAt);
|
|
2658
|
+
const added = this.addPendingSyncClaim(coordinateOrHash, from, admission.expiresAt);
|
|
2659
|
+
if (added) {
|
|
2660
|
+
requestHashes.push(coordinateOrHash);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
else {
|
|
2665
|
+
const has = options?.skipCheck !== true &&
|
|
2666
|
+
(await this.checkHasCoordinateOrHash(coordinateOrHash, knownKeys));
|
|
2667
|
+
if (!isCapturedLifecycleActive()) {
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
if (has) {
|
|
2671
|
+
this.clearPendingSyncAdmissionIdentity(identity);
|
|
2616
2672
|
continue;
|
|
2617
2673
|
}
|
|
2618
|
-
this.
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
requestHashes.push(coordinateOrHash);
|
|
2674
|
+
const consumption = this.consumePendingSyncAdmission(admission, identity);
|
|
2675
|
+
if (consumption === "invalid") {
|
|
2676
|
+
return;
|
|
2622
2677
|
}
|
|
2678
|
+
if (consumption === "settled") {
|
|
2679
|
+
continue;
|
|
2680
|
+
}
|
|
2681
|
+
// Track the initial sender so we can retry if the first request is lost.
|
|
2682
|
+
this.addPendingSyncClaim(coordinateOrHash, from, admission.expiresAt);
|
|
2683
|
+
requestHashes.push(coordinateOrHash); // request immediately (first time we have seen this hash)
|
|
2623
2684
|
}
|
|
2624
2685
|
}
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
this.clearPendingSyncAdmissionIdentity(identity);
|
|
2633
|
-
continue;
|
|
2634
|
-
}
|
|
2635
|
-
const consumption = this.consumePendingSyncAdmission(admission, identity);
|
|
2636
|
-
if (consumption === "invalid") {
|
|
2637
|
-
return;
|
|
2638
|
-
}
|
|
2639
|
-
if (consumption === "settled") {
|
|
2640
|
-
continue;
|
|
2641
|
-
}
|
|
2642
|
-
// Track the initial sender so we can retry if the first request is lost.
|
|
2643
|
-
this.addPendingSyncClaim(coordinateOrHash, from, admission.expiresAt);
|
|
2644
|
-
requestHashes.push(coordinateOrHash); // request immediately (first time we have seen this hash)
|
|
2686
|
+
if (profile) {
|
|
2687
|
+
emitSyncProfileDuration(profile, loopStartedAt, {
|
|
2688
|
+
name: "simple.queueSync.plan",
|
|
2689
|
+
entries: keysToCheck.length,
|
|
2690
|
+
count: requestHashes.length,
|
|
2691
|
+
targets: 1,
|
|
2692
|
+
});
|
|
2645
2693
|
}
|
|
2694
|
+
// Persistent admission work is complete. Do not let an unrelated
|
|
2695
|
+
// blocked transport send retain unused quota.
|
|
2696
|
+
this.releasePendingSyncAdmission(admission);
|
|
2697
|
+
const dispatchableRequestHashes = this.filterDispatchablePendingSyncClaims(requestHashes, fromHash, targetEpoch);
|
|
2698
|
+
dispatchableRequestHashes.length > 0 &&
|
|
2699
|
+
(await this.requestSync(dispatchableRequestHashes, [fromHash], {
|
|
2700
|
+
signal: options?.signal,
|
|
2701
|
+
ownershipLifecycleController,
|
|
2702
|
+
targetEpochs: new Map([[fromHash, targetEpoch]]),
|
|
2703
|
+
createTargetEpochs: false,
|
|
2704
|
+
}));
|
|
2646
2705
|
}
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
name: "simple.queueSync.plan",
|
|
2650
|
-
entries: keysToCheck.length,
|
|
2651
|
-
count: requestHashes.length,
|
|
2652
|
-
targets: 1,
|
|
2653
|
-
});
|
|
2706
|
+
finally {
|
|
2707
|
+
this.releasePendingSyncAdmission(admission);
|
|
2654
2708
|
}
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
ownershipLifecycleController,
|
|
2662
|
-
targetEpochs: new Map([[fromHash, targetEpoch]]),
|
|
2663
|
-
createTargetEpochs: false,
|
|
2664
|
-
}));
|
|
2665
|
-
await existingRequest;
|
|
2666
|
-
}
|
|
2667
|
-
finally {
|
|
2668
|
-
this.releasePendingSyncAdmission(admission);
|
|
2709
|
+
})();
|
|
2710
|
+
// Admission quota covers the finished lookup only. The eager dispatch
|
|
2711
|
+
// retains its own physical lifecycle, and every receive exit joins it.
|
|
2712
|
+
const results = await Promise.allSettled([planning, existingRequest]);
|
|
2713
|
+
const errors = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
2714
|
+
try {
|
|
2669
2715
|
if (profile) {
|
|
2670
2716
|
emitSyncProfileDuration(profile, startedAt, {
|
|
2671
2717
|
name: "simple.queueSync",
|
|
@@ -2679,13 +2725,23 @@ export class SimpleSyncronizer {
|
|
|
2679
2725
|
});
|
|
2680
2726
|
}
|
|
2681
2727
|
}
|
|
2728
|
+
catch (error) {
|
|
2729
|
+
errors.push(error);
|
|
2730
|
+
}
|
|
2731
|
+
if (errors.length === 1)
|
|
2732
|
+
throw errors[0];
|
|
2733
|
+
if (errors.length > 1) {
|
|
2734
|
+
throw new AggregateError(errors, "sync lookup and eager response failed", {
|
|
2735
|
+
cause: errors[0],
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2682
2738
|
}
|
|
2683
2739
|
async requestSync(hashes, to, options) {
|
|
2684
2740
|
const targets = [...new Set(to)];
|
|
2685
2741
|
if (hashes.length === 0 || targets.length === 0) {
|
|
2686
2742
|
return;
|
|
2687
2743
|
}
|
|
2688
|
-
const lifecycle = this.captureSyncDispatchLifecycle(targets,
|
|
2744
|
+
const lifecycle = this.captureSyncDispatchLifecycle(targets, options?.signal, {
|
|
2689
2745
|
ownershipLifecycleController: options?.ownershipLifecycleController,
|
|
2690
2746
|
targetEpochs: options?.targetEpochs,
|
|
2691
2747
|
createTargetEpochs: options?.createTargetEpochs,
|
|
@@ -2745,7 +2801,7 @@ export class SimpleSyncronizer {
|
|
|
2745
2801
|
coordinateMessages += 1;
|
|
2746
2802
|
}
|
|
2747
2803
|
catch (error) {
|
|
2748
|
-
if (!this.isSyncDispatchLifecycleActive(lifecycle, target)) {
|
|
2804
|
+
if (isSyncDispatchCancellation(error, this.getSyncDispatchSignal(lifecycle, target), !this.isSyncDispatchLifecycleActive(lifecycle, target))) {
|
|
2749
2805
|
break;
|
|
2750
2806
|
}
|
|
2751
2807
|
throw error;
|
|
@@ -2778,7 +2834,7 @@ export class SimpleSyncronizer {
|
|
|
2778
2834
|
stringMessages += 1;
|
|
2779
2835
|
}
|
|
2780
2836
|
catch (error) {
|
|
2781
|
-
if (!this.isSyncDispatchLifecycleActive(lifecycle, target)) {
|
|
2837
|
+
if (isSyncDispatchCancellation(error, this.getSyncDispatchSignal(lifecycle, target), !this.isSyncDispatchLifecycleActive(lifecycle, target))) {
|
|
2782
2838
|
break;
|
|
2783
2839
|
}
|
|
2784
2840
|
throw error;
|
|
@@ -3026,9 +3082,12 @@ export class SimpleSyncronizer {
|
|
|
3026
3082
|
// Defensive observation for failures outside the loop's best-effort body.
|
|
3027
3083
|
});
|
|
3028
3084
|
}
|
|
3029
|
-
|
|
3085
|
+
beginClose() {
|
|
3030
3086
|
this.closed = true;
|
|
3031
3087
|
this.syncDispatchLifecycleController.abort();
|
|
3088
|
+
}
|
|
3089
|
+
async close() {
|
|
3090
|
+
this.beginClose();
|
|
3032
3091
|
this.syncDispatchTargetEpochs.clear();
|
|
3033
3092
|
this.clearPendingSyncAdmissions();
|
|
3034
3093
|
this.syncInFlightRetryIterator = undefined;
|