@peerbit/shared-log 12.2.0 → 12.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/pid-convergence.d.ts +2 -0
- package/dist/benchmark/pid-convergence.d.ts.map +1 -0
- package/dist/benchmark/pid-convergence.js +138 -0
- package/dist/benchmark/pid-convergence.js.map +1 -0
- package/dist/benchmark/rateless-iblt-sender-startsync.d.ts +2 -0
- package/dist/benchmark/rateless-iblt-sender-startsync.d.ts.map +1 -0
- package/dist/benchmark/rateless-iblt-sender-startsync.js +104 -0
- package/dist/benchmark/rateless-iblt-sender-startsync.js.map +1 -0
- package/dist/benchmark/rateless-iblt-startsync-cache.d.ts +2 -0
- package/dist/benchmark/rateless-iblt-startsync-cache.d.ts.map +1 -0
- package/dist/benchmark/rateless-iblt-startsync-cache.js +112 -0
- package/dist/benchmark/rateless-iblt-startsync-cache.js.map +1 -0
- package/dist/benchmark/sync-catchup.d.ts +3 -0
- package/dist/benchmark/sync-catchup.d.ts.map +1 -0
- package/dist/benchmark/sync-catchup.js +109 -0
- package/dist/benchmark/sync-catchup.js.map +1 -0
- package/dist/src/index.d.ts +16 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +259 -82
- package/dist/src/index.js.map +1 -1
- package/dist/src/ranges.d.ts +1 -0
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +48 -18
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/sync/index.d.ts +14 -0
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.d.ts +14 -22
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +137 -22
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +3 -1
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +23 -1
- package/dist/src/sync/simple.js.map +1 -1
- package/package.json +12 -12
- package/src/index.ts +333 -126
- package/src/ranges.ts +97 -65
- package/src/sync/index.ts +19 -0
- package/src/sync/rateless-iblt.ts +187 -41
- package/src/sync/simple.ts +25 -2
package/src/ranges.ts
CHANGED
|
@@ -2064,6 +2064,7 @@ export const getSamples = async <R extends "u32" | "u64">(
|
|
|
2064
2064
|
options?: {
|
|
2065
2065
|
onlyIntersecting?: boolean;
|
|
2066
2066
|
uniqueReplicators?: Set<string>;
|
|
2067
|
+
peerFilter?: Set<string>;
|
|
2067
2068
|
},
|
|
2068
2069
|
): Promise<Map<string, { intersecting: boolean }>> => {
|
|
2069
2070
|
const leaders: Map<string, { intersecting: boolean }> = new Map();
|
|
@@ -2075,6 +2076,7 @@ export const getSamples = async <R extends "u32" | "u64">(
|
|
|
2075
2076
|
let matured = 0;
|
|
2076
2077
|
|
|
2077
2078
|
let uniqueVisited = new Set<string>();
|
|
2079
|
+
const peerFilter = options?.peerFilter;
|
|
2078
2080
|
for (let i = 0; i < cursor.length; i++) {
|
|
2079
2081
|
let point = cursor[i];
|
|
2080
2082
|
|
|
@@ -2084,6 +2086,9 @@ export const getSamples = async <R extends "u32" | "u64">(
|
|
|
2084
2086
|
);
|
|
2085
2087
|
|
|
2086
2088
|
for (const rect of allContaining) {
|
|
2089
|
+
if (peerFilter && !peerFilter.has(rect.value.hash)) {
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2087
2092
|
uniqueVisited.add(rect.value.hash);
|
|
2088
2093
|
let prev = leaders.get(rect.value.hash);
|
|
2089
2094
|
if (!prev) {
|
|
@@ -2096,7 +2101,7 @@ export const getSamples = async <R extends "u32" | "u64">(
|
|
|
2096
2101
|
}
|
|
2097
2102
|
}
|
|
2098
2103
|
|
|
2099
|
-
if (options?.uniqueReplicators) {
|
|
2104
|
+
if (options?.uniqueReplicators && options.uniqueReplicators.size > 0) {
|
|
2100
2105
|
if (
|
|
2101
2106
|
options.uniqueReplicators.size === leaders.size ||
|
|
2102
2107
|
options.uniqueReplicators.size === uniqueVisited.size
|
|
@@ -2114,6 +2119,9 @@ export const getSamples = async <R extends "u32" | "u64">(
|
|
|
2114
2119
|
roleAge,
|
|
2115
2120
|
peers,
|
|
2116
2121
|
(rect, m) => {
|
|
2122
|
+
if (peerFilter && !peerFilter.has(rect.hash)) {
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2117
2125
|
uniqueVisited.add(rect.hash);
|
|
2118
2126
|
const prev = leaders.get(rect.hash);
|
|
2119
2127
|
if (m) {
|
|
@@ -2234,7 +2242,7 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
2234
2242
|
nextLocation: NumberFromType<R>,
|
|
2235
2243
|
roleAge: number,
|
|
2236
2244
|
) => {
|
|
2237
|
-
|
|
2245
|
+
const next = await fetchOne(
|
|
2238
2246
|
iterateRangesContainingPoint<undefined, R>(peers, nextLocation, {
|
|
2239
2247
|
sort: [new Sort({ key: "end2", direction: SortDirection.DESC })],
|
|
2240
2248
|
time: {
|
|
@@ -2243,31 +2251,31 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
2243
2251
|
now,
|
|
2244
2252
|
},
|
|
2245
2253
|
}),
|
|
2246
|
-
); // get
|
|
2254
|
+
); // get intersecting sort by largest end2
|
|
2247
2255
|
return next;
|
|
2248
2256
|
};
|
|
2249
2257
|
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2258
|
+
const resolveNextAbove = async (
|
|
2259
|
+
nextLocation: NumberFromType<R>,
|
|
2260
|
+
roleAge: number,
|
|
2261
|
+
) => {
|
|
2262
|
+
// if not get closest from above
|
|
2263
|
+
const next = await fetchOne<undefined, R>(
|
|
2264
|
+
getClosest("above", peers, nextLocation, true, properties.numbers, {
|
|
2265
|
+
time: {
|
|
2266
|
+
matured: true,
|
|
2267
|
+
roleAgeLimit: roleAge,
|
|
2268
|
+
now,
|
|
2269
|
+
},
|
|
2270
|
+
}),
|
|
2271
|
+
);
|
|
2272
|
+
return next;
|
|
2273
|
+
};
|
|
2266
2274
|
|
|
2267
2275
|
const resolveNext = async (
|
|
2268
2276
|
nextLocation: NumberFromType<R>,
|
|
2269
2277
|
roleAge: number,
|
|
2270
|
-
): Promise<[ReplicationRangeIndexable<R
|
|
2278
|
+
): Promise<[ReplicationRangeIndexable<R> | undefined, boolean]> => {
|
|
2271
2279
|
const containing = await resolveNextContaining(nextLocation, roleAge);
|
|
2272
2280
|
if (containing) {
|
|
2273
2281
|
return [containing, true];
|
|
@@ -2288,12 +2296,25 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
2288
2296
|
from: NumberFromType<R>,
|
|
2289
2297
|
) => {
|
|
2290
2298
|
const toEnd2 = properties.numbers.increment(to.end2); // TODO investigate why this is needed
|
|
2291
|
-
if (toEnd2 < from
|
|
2299
|
+
if (toEnd2 < from) {
|
|
2292
2300
|
wrappedOnce = true;
|
|
2293
2301
|
// @ts-ignore
|
|
2294
2302
|
coveredLength += properties.numbers.maxValue - from;
|
|
2295
2303
|
// @ts-ignore
|
|
2296
2304
|
coveredLength += toEnd2;
|
|
2305
|
+
} else if (to.wrapped) {
|
|
2306
|
+
// When the range is wrapped and `from` is in the second segment (near zero),
|
|
2307
|
+
// the distance to `end2` does not wrap. Otherwise we must wrap to reach `end2`.
|
|
2308
|
+
if (from < to.end2) {
|
|
2309
|
+
// @ts-ignore
|
|
2310
|
+
coveredLength += toEnd2 - from;
|
|
2311
|
+
} else {
|
|
2312
|
+
wrappedOnce = true;
|
|
2313
|
+
// @ts-ignore
|
|
2314
|
+
coveredLength += properties.numbers.maxValue - from;
|
|
2315
|
+
// @ts-ignore
|
|
2316
|
+
coveredLength += toEnd2;
|
|
2317
|
+
}
|
|
2297
2318
|
} else {
|
|
2298
2319
|
// @ts-ignore
|
|
2299
2320
|
coveredLength += to.end1 - from;
|
|
@@ -2314,6 +2335,7 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
2314
2335
|
(coveredLength <= properties.numbers.maxValue || !wrappedOnce) // eslint-disable-line no-unmodified-loop-condition
|
|
2315
2336
|
) {
|
|
2316
2337
|
let distanceBefore = coveredLength;
|
|
2338
|
+
const nextLocationBefore = nextLocation;
|
|
2317
2339
|
|
|
2318
2340
|
let nextCandidate = await resolveNext(nextLocation, roleAge);
|
|
2319
2341
|
let matured = true;
|
|
@@ -2343,55 +2365,53 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
2343
2365
|
let last = current;
|
|
2344
2366
|
current = nextCandidate[0];
|
|
2345
2367
|
|
|
2346
|
-
|
|
2347
|
-
distanceBefore < widthToCoverScaled &&
|
|
2348
|
-
|
|
2368
|
+
const isLast =
|
|
2369
|
+
distanceBefore < widthToCoverScaled && coveredLength >= widthToCoverScaled;
|
|
2370
|
+
|
|
2371
|
+
const lastDistanceToEndLocation = properties.numbers.min(
|
|
2372
|
+
getDistance(
|
|
2373
|
+
last.start1,
|
|
2374
|
+
endLocation,
|
|
2375
|
+
"closest",
|
|
2376
|
+
properties.numbers.maxValue,
|
|
2377
|
+
),
|
|
2378
|
+
getDistance(
|
|
2379
|
+
last.end2,
|
|
2380
|
+
endLocation,
|
|
2381
|
+
"closest",
|
|
2382
|
+
properties.numbers.maxValue,
|
|
2383
|
+
),
|
|
2384
|
+
);
|
|
2385
|
+
|
|
2386
|
+
const currentDistanceToEndLocation = properties.numbers.min(
|
|
2387
|
+
getDistance(
|
|
2388
|
+
current.start1,
|
|
2389
|
+
endLocation,
|
|
2390
|
+
"closest",
|
|
2391
|
+
properties.numbers.maxValue,
|
|
2392
|
+
),
|
|
2393
|
+
getDistance(
|
|
2394
|
+
current.end2,
|
|
2395
|
+
endLocation,
|
|
2396
|
+
"closest",
|
|
2397
|
+
properties.numbers.maxValue,
|
|
2398
|
+
),
|
|
2399
|
+
);
|
|
2349
2400
|
|
|
2350
2401
|
if (
|
|
2351
2402
|
!isLast ||
|
|
2352
2403
|
nextCandidate[1] ||
|
|
2353
|
-
|
|
2354
|
-
getDistance(
|
|
2355
|
-
last.start1,
|
|
2356
|
-
endLocation,
|
|
2357
|
-
"closest",
|
|
2358
|
-
properties.numbers.maxValue,
|
|
2359
|
-
),
|
|
2360
|
-
getDistance(
|
|
2361
|
-
last.end2,
|
|
2362
|
-
endLocation,
|
|
2363
|
-
"closest",
|
|
2364
|
-
properties.numbers.maxValue,
|
|
2365
|
-
),
|
|
2366
|
-
) >
|
|
2367
|
-
properties.numbers.min(
|
|
2368
|
-
getDistance(
|
|
2369
|
-
current.start1,
|
|
2370
|
-
endLocation,
|
|
2371
|
-
"closest",
|
|
2372
|
-
properties.numbers.maxValue,
|
|
2373
|
-
),
|
|
2374
|
-
getDistance(
|
|
2375
|
-
current.end2,
|
|
2376
|
-
endLocation,
|
|
2377
|
-
"closest",
|
|
2378
|
-
properties.numbers.maxValue,
|
|
2379
|
-
),
|
|
2380
|
-
)
|
|
2404
|
+
lastDistanceToEndLocation >= currentDistanceToEndLocation
|
|
2381
2405
|
) {
|
|
2382
2406
|
ret.add(current.hash);
|
|
2383
2407
|
}
|
|
2384
2408
|
|
|
2385
|
-
if (isLast && !nextCandidate[1] /* || equals(endRect.id, current.id) */) {
|
|
2386
|
-
break;
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
2409
|
if (matured) {
|
|
2390
2410
|
maturedCoveredLength = coveredLength;
|
|
2391
2411
|
}
|
|
2392
2412
|
|
|
2393
2413
|
let startForNext = extraDistanceForNext
|
|
2394
|
-
? properties.numbers.increment(
|
|
2414
|
+
? properties.numbers.increment(nextLocation)
|
|
2395
2415
|
: current.end2;
|
|
2396
2416
|
nextLocation = endIsWrapped
|
|
2397
2417
|
? wrappedOnce
|
|
@@ -2399,6 +2419,15 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
2399
2419
|
: startForNext
|
|
2400
2420
|
: properties.numbers.min(startForNext, endLocation);
|
|
2401
2421
|
|
|
2422
|
+
// Safety: ensure we always make progress to avoid infinite loops (can happen when
|
|
2423
|
+
// the chosen range is the same and `nextLocation` doesn't advance).
|
|
2424
|
+
if (
|
|
2425
|
+
nextLocation === nextLocationBefore &&
|
|
2426
|
+
coveredLength === distanceBefore
|
|
2427
|
+
) {
|
|
2428
|
+
break;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2402
2431
|
if (
|
|
2403
2432
|
(typeof nextLocation === "bigint" &&
|
|
2404
2433
|
nextLocation === (endLocation as bigint)) ||
|
|
@@ -2603,19 +2632,19 @@ export const mergeReplicationChanges = <R extends NumericType>(
|
|
|
2603
2632
|
let results: ReplicationChange<ReplicationRangeIndexable<R>>[] = [];
|
|
2604
2633
|
let consumed: Set<number> = new Set();
|
|
2605
2634
|
for (let i = 0; i < v.length; i++) {
|
|
2606
|
-
//
|
|
2607
|
-
//
|
|
2608
|
-
// equivalent is that this would represent (1 - 1 + 1) = 1
|
|
2635
|
+
// If segment is removed and we have previously processed it then go over each
|
|
2636
|
+
// overlapping added segment and remove the overlap. Equivalent to: (1 - 1 + 1) = 1.
|
|
2609
2637
|
if (v[i].type === "removed" || v[i].type === "replaced") {
|
|
2610
2638
|
if (rebalanceHistory.has(v[i].range.rangeHash)) {
|
|
2611
|
-
let
|
|
2639
|
+
let adjusted = false;
|
|
2640
|
+
const vStart = v.length;
|
|
2612
2641
|
for (let j = i + 1; j < vStart; j++) {
|
|
2613
2642
|
const newer = v[j];
|
|
2614
2643
|
if (newer.type === "added" && !newer.matured) {
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2644
|
+
adjusted = true;
|
|
2645
|
+
const { rangesFromA: updatedRemoved, rangesFromB: updatedNewer } =
|
|
2646
|
+
symmetricDifferenceRanges(v[i].range, newer.range);
|
|
2647
|
+
|
|
2619
2648
|
for (const diff of updatedRemoved) {
|
|
2620
2649
|
results.push({
|
|
2621
2650
|
range: diff,
|
|
@@ -2634,6 +2663,9 @@ export const mergeReplicationChanges = <R extends NumericType>(
|
|
|
2634
2663
|
}
|
|
2635
2664
|
}
|
|
2636
2665
|
rebalanceHistory.del(v[i].range.rangeHash);
|
|
2666
|
+
if (!adjusted) {
|
|
2667
|
+
results.push(v[i]);
|
|
2668
|
+
}
|
|
2637
2669
|
} else {
|
|
2638
2670
|
results.push(v[i]);
|
|
2639
2671
|
}
|
package/src/sync/index.ts
CHANGED
|
@@ -8,6 +8,24 @@ import type { Numbers } from "../integers.js";
|
|
|
8
8
|
import type { TransportMessage } from "../message.js";
|
|
9
9
|
import type { EntryReplicated, ReplicationRangeIndexable } from "../ranges.js";
|
|
10
10
|
|
|
11
|
+
export type SyncPriorityFn<R extends "u32" | "u64"> = (
|
|
12
|
+
entry: EntryReplicated<R>,
|
|
13
|
+
) => number;
|
|
14
|
+
|
|
15
|
+
export type SyncOptions<R extends "u32" | "u64"> = {
|
|
16
|
+
/**
|
|
17
|
+
* Higher numbers are synced first.
|
|
18
|
+
* The callback should be fast and side-effect free.
|
|
19
|
+
*/
|
|
20
|
+
priority?: SyncPriorityFn<R>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* When using rateless IBLT sync, optionally pre-sync up to this many
|
|
24
|
+
* high-priority entries using the simple synchronizer.
|
|
25
|
+
*/
|
|
26
|
+
maxSimpleEntries?: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
11
29
|
export type SynchronizerComponents<R extends "u32" | "u64"> = {
|
|
12
30
|
rpc: RPC<TransportMessage, TransportMessage>;
|
|
13
31
|
rangeIndex: Index<ReplicationRangeIndexable<R>, any>;
|
|
@@ -15,6 +33,7 @@ export type SynchronizerComponents<R extends "u32" | "u64"> = {
|
|
|
15
33
|
log: Log<any>;
|
|
16
34
|
coordinateToHash: Cache<string>;
|
|
17
35
|
numbers: Numbers<R>;
|
|
36
|
+
sync?: SyncOptions<R>;
|
|
18
37
|
};
|
|
19
38
|
export type SynchronizerConstructor<R extends "u32" | "u64"> = new (
|
|
20
39
|
properties: SynchronizerComponents<R>,
|
|
@@ -12,14 +12,16 @@ import {
|
|
|
12
12
|
import type { RPC, RequestContext } from "@peerbit/rpc";
|
|
13
13
|
import { SilentDelivery } from "@peerbit/stream-interface";
|
|
14
14
|
import { type EntryWithRefs } from "../exchange-heads.js";
|
|
15
|
-
import { type Numbers } from "../integers.js";
|
|
16
15
|
import { TransportMessage } from "../message.js";
|
|
17
16
|
import {
|
|
18
17
|
type EntryReplicated,
|
|
19
|
-
type ReplicationRangeIndexable,
|
|
20
18
|
matchEntriesInRangeQuery,
|
|
21
19
|
} from "../ranges.js";
|
|
22
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
SyncableKey,
|
|
22
|
+
SynchronizerComponents,
|
|
23
|
+
Syncronizer,
|
|
24
|
+
} from "./index.js";
|
|
23
25
|
import { SimpleSyncronizer } from "./simple.js";
|
|
24
26
|
|
|
25
27
|
export const logger = loggerFn("peerbit:shared-log:rateless");
|
|
@@ -185,6 +187,13 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
185
187
|
simple: SimpleSyncronizer<D>;
|
|
186
188
|
|
|
187
189
|
startedOrCompletedSynchronizations: Cache<string>;
|
|
190
|
+
private localRangeEncoderCacheVersion = 0;
|
|
191
|
+
private localRangeEncoderCache: Map<
|
|
192
|
+
string,
|
|
193
|
+
{ encoder: EncoderWrapper; version: number; lastUsed: number }
|
|
194
|
+
> = new Map();
|
|
195
|
+
private localRangeEncoderCacheMax = 2;
|
|
196
|
+
|
|
188
197
|
ingoingSyncProcesses: Map<
|
|
189
198
|
string,
|
|
190
199
|
{
|
|
@@ -212,14 +221,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
212
221
|
>;
|
|
213
222
|
|
|
214
223
|
constructor(
|
|
215
|
-
readonly properties:
|
|
216
|
-
rpc: RPC<TransportMessage, TransportMessage>;
|
|
217
|
-
rangeIndex: Index<ReplicationRangeIndexable<D>, any>;
|
|
218
|
-
entryIndex: Index<EntryReplicated<D>, any>;
|
|
219
|
-
log: Log<any>;
|
|
220
|
-
coordinateToHash: Cache<string>;
|
|
221
|
-
numbers: Numbers<D>;
|
|
222
|
-
},
|
|
224
|
+
readonly properties: SynchronizerComponents<D>,
|
|
223
225
|
) {
|
|
224
226
|
this.simple = new SimpleSyncronizer(properties);
|
|
225
227
|
this.outgoingSyncProcesses = new Map();
|
|
@@ -227,6 +229,91 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
227
229
|
this.startedOrCompletedSynchronizations = new Cache({ max: 1e4 });
|
|
228
230
|
}
|
|
229
231
|
|
|
232
|
+
private clearLocalRangeEncoderCache() {
|
|
233
|
+
for (const [, cached] of this.localRangeEncoderCache) {
|
|
234
|
+
cached.encoder.free();
|
|
235
|
+
}
|
|
236
|
+
this.localRangeEncoderCache.clear();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private invalidateLocalRangeEncoderCache() {
|
|
240
|
+
this.localRangeEncoderCacheVersion += 1;
|
|
241
|
+
this.clearLocalRangeEncoderCache();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private localRangeEncoderCacheKey(ranges: {
|
|
245
|
+
start1: NumberOrBigint;
|
|
246
|
+
end1: NumberOrBigint;
|
|
247
|
+
start2: NumberOrBigint;
|
|
248
|
+
end2: NumberOrBigint;
|
|
249
|
+
}) {
|
|
250
|
+
return `${String(ranges.start1)}:${String(ranges.end1)}:${String(
|
|
251
|
+
ranges.start2,
|
|
252
|
+
)}:${String(ranges.end2)}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private decoderFromCachedEncoder(encoder: EncoderWrapper): DecoderWrapper {
|
|
256
|
+
const clone = encoder.clone();
|
|
257
|
+
const decoder = clone.to_decoder();
|
|
258
|
+
clone.free();
|
|
259
|
+
return decoder;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private async getLocalDecoderForRange(ranges: {
|
|
263
|
+
start1: NumberOrBigint;
|
|
264
|
+
end1: NumberOrBigint;
|
|
265
|
+
start2: NumberOrBigint;
|
|
266
|
+
end2: NumberOrBigint;
|
|
267
|
+
}): Promise<DecoderWrapper | false> {
|
|
268
|
+
const key = this.localRangeEncoderCacheKey(ranges);
|
|
269
|
+
const cached = this.localRangeEncoderCache.get(key);
|
|
270
|
+
if (cached && cached.version === this.localRangeEncoderCacheVersion) {
|
|
271
|
+
cached.lastUsed = Date.now();
|
|
272
|
+
return this.decoderFromCachedEncoder(cached.encoder);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const encoder = (await buildEncoderOrDecoderFromRange(
|
|
276
|
+
ranges,
|
|
277
|
+
this.properties.entryIndex,
|
|
278
|
+
"encoder",
|
|
279
|
+
)) as EncoderWrapper | false;
|
|
280
|
+
if (!encoder) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const now = Date.now();
|
|
285
|
+
const existing = this.localRangeEncoderCache.get(key);
|
|
286
|
+
if (existing) {
|
|
287
|
+
existing.encoder.free();
|
|
288
|
+
}
|
|
289
|
+
this.localRangeEncoderCache.set(key, {
|
|
290
|
+
encoder,
|
|
291
|
+
version: this.localRangeEncoderCacheVersion,
|
|
292
|
+
lastUsed: now,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
while (this.localRangeEncoderCache.size > this.localRangeEncoderCacheMax) {
|
|
296
|
+
let oldestKey: string | undefined;
|
|
297
|
+
let oldestUsed = Number.POSITIVE_INFINITY;
|
|
298
|
+
for (const [candidateKey, value] of this.localRangeEncoderCache) {
|
|
299
|
+
if (value.lastUsed < oldestUsed) {
|
|
300
|
+
oldestUsed = value.lastUsed;
|
|
301
|
+
oldestKey = candidateKey;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (!oldestKey) {
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
const victim = this.localRangeEncoderCache.get(oldestKey);
|
|
308
|
+
if (victim) {
|
|
309
|
+
victim.encoder.free();
|
|
310
|
+
}
|
|
311
|
+
this.localRangeEncoderCache.delete(oldestKey);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return this.decoderFromCachedEncoder(encoder);
|
|
315
|
+
}
|
|
316
|
+
|
|
230
317
|
async onMaybeMissingEntries(properties: {
|
|
231
318
|
entries: Map<string, EntryReplicated<D>>;
|
|
232
319
|
targets: string[];
|
|
@@ -238,7 +325,6 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
238
325
|
// such as those assigned to range boundaries.
|
|
239
326
|
|
|
240
327
|
let entriesToSyncNaively: Map<string, EntryReplicated<D>> = new Map();
|
|
241
|
-
let allCoordinatesToSyncWithIblt: bigint[] = [];
|
|
242
328
|
let minSyncIbltSize = 333; // TODO: make configurable
|
|
243
329
|
let maxSyncWithSimpleMethod = 1e3;
|
|
244
330
|
|
|
@@ -251,15 +337,59 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
251
337
|
return;
|
|
252
338
|
}
|
|
253
339
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Mixed strategy for larger batches
|
|
340
|
+
const nonBoundaryEntries: EntryReplicated<D>[] = [];
|
|
257
341
|
for (const entry of properties.entries.values()) {
|
|
258
342
|
if (entry.assignedToRangeBoundary) {
|
|
259
343
|
entriesToSyncNaively.set(entry.hash, entry);
|
|
260
344
|
} else {
|
|
261
|
-
|
|
345
|
+
nonBoundaryEntries.push(entry);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const priorityFn = this.properties.sync?.priority;
|
|
350
|
+
const maxSimpleEntries = this.properties.sync?.maxSimpleEntries;
|
|
351
|
+
const maxAdditionalNaive =
|
|
352
|
+
priorityFn &&
|
|
353
|
+
typeof maxSimpleEntries === "number" &&
|
|
354
|
+
Number.isFinite(maxSimpleEntries) &&
|
|
355
|
+
maxSimpleEntries > 0
|
|
356
|
+
? Math.max(
|
|
357
|
+
0,
|
|
358
|
+
Math.min(
|
|
359
|
+
Math.floor(maxSimpleEntries),
|
|
360
|
+
maxSyncWithSimpleMethod - entriesToSyncNaively.size,
|
|
361
|
+
),
|
|
362
|
+
)
|
|
363
|
+
: 0;
|
|
364
|
+
|
|
365
|
+
if (priorityFn && maxAdditionalNaive > 0 && nonBoundaryEntries.length > 0) {
|
|
366
|
+
let index = 0;
|
|
367
|
+
const scored: {
|
|
368
|
+
entry: EntryReplicated<D>;
|
|
369
|
+
index: number;
|
|
370
|
+
priority: number;
|
|
371
|
+
}[] = [];
|
|
372
|
+
for (const entry of nonBoundaryEntries) {
|
|
373
|
+
const priorityValue = priorityFn(entry);
|
|
374
|
+
scored.push({
|
|
375
|
+
entry,
|
|
376
|
+
index,
|
|
377
|
+
priority: Number.isFinite(priorityValue) ? priorityValue : 0,
|
|
378
|
+
});
|
|
379
|
+
index += 1;
|
|
380
|
+
}
|
|
381
|
+
scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
|
|
382
|
+
for (const { entry } of scored.slice(0, maxAdditionalNaive)) {
|
|
383
|
+
entriesToSyncNaively.set(entry.hash, entry);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let allCoordinatesToSyncWithIblt: bigint[] = [];
|
|
388
|
+
for (const entry of nonBoundaryEntries) {
|
|
389
|
+
if (entriesToSyncNaively.has(entry.hash)) {
|
|
390
|
+
continue;
|
|
262
391
|
}
|
|
392
|
+
allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
|
|
263
393
|
}
|
|
264
394
|
|
|
265
395
|
if (entriesToSyncNaively.size > 0) {
|
|
@@ -275,31 +405,44 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
275
405
|
entriesToSyncNaively.size > maxSyncWithSimpleMethod
|
|
276
406
|
) {
|
|
277
407
|
// Fallback: if nothing left for IBLT (or simple set is too large), include all in IBLT
|
|
278
|
-
allCoordinatesToSyncWithIblt =
|
|
279
|
-
|
|
280
|
-
|
|
408
|
+
allCoordinatesToSyncWithIblt = [];
|
|
409
|
+
for (const entry of properties.entries.values()) {
|
|
410
|
+
allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
|
|
411
|
+
}
|
|
281
412
|
}
|
|
282
413
|
|
|
283
414
|
if (allCoordinatesToSyncWithIblt.length === 0) {
|
|
284
415
|
return;
|
|
285
416
|
}
|
|
286
417
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
418
|
+
await ribltReady;
|
|
419
|
+
|
|
420
|
+
let sortedEntries: bigint[] | BigUint64Array;
|
|
421
|
+
if (typeof BigUint64Array !== "undefined") {
|
|
422
|
+
const typed = new BigUint64Array(allCoordinatesToSyncWithIblt.length);
|
|
423
|
+
for (let i = 0; i < allCoordinatesToSyncWithIblt.length; i++) {
|
|
424
|
+
typed[i] = allCoordinatesToSyncWithIblt[i];
|
|
294
425
|
}
|
|
295
|
-
|
|
426
|
+
typed.sort();
|
|
427
|
+
sortedEntries = typed;
|
|
428
|
+
} else {
|
|
429
|
+
sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
|
|
430
|
+
if (a > b) {
|
|
431
|
+
return 1;
|
|
432
|
+
} else if (a < b) {
|
|
433
|
+
return -1;
|
|
434
|
+
} else {
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
296
439
|
|
|
297
440
|
// assume sorted, and find the largest gap
|
|
298
441
|
let largestGap = 0n;
|
|
299
442
|
let largestGapIndex = 0;
|
|
300
|
-
for (let i = 0; i < sortedEntries.length
|
|
443
|
+
for (let i = 0; i < sortedEntries.length; i++) {
|
|
301
444
|
const current = sortedEntries[i];
|
|
302
|
-
const next = sortedEntries[i + 1];
|
|
445
|
+
const next = sortedEntries[(i + 1) % sortedEntries.length];
|
|
303
446
|
const gap =
|
|
304
447
|
next >= current
|
|
305
448
|
? next - current
|
|
@@ -329,8 +472,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
329
472
|
|
|
330
473
|
const startSync = new StartSync({ from: start, to: end, symbols: [] });
|
|
331
474
|
const encoder = new EncoderWrapper();
|
|
332
|
-
|
|
333
|
-
encoder.
|
|
475
|
+
if (typeof BigUint64Array !== "undefined" && sortedEntries instanceof BigUint64Array) {
|
|
476
|
+
encoder.add_symbols(sortedEntries);
|
|
477
|
+
} else {
|
|
478
|
+
for (const entry of sortedEntries) {
|
|
479
|
+
encoder.add_symbol(coerceBigInt(entry));
|
|
480
|
+
}
|
|
334
481
|
}
|
|
335
482
|
|
|
336
483
|
let initialSymbols = Math.round(
|
|
@@ -406,16 +553,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
406
553
|
this.startedOrCompletedSynchronizations.add(syncId);
|
|
407
554
|
|
|
408
555
|
const wrapped = message.end < message.start;
|
|
409
|
-
const decoder = await
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
},
|
|
416
|
-
this.properties.entryIndex,
|
|
417
|
-
"decoder",
|
|
418
|
-
);
|
|
556
|
+
const decoder = await this.getLocalDecoderForRange({
|
|
557
|
+
start1: message.start,
|
|
558
|
+
end1: wrapped ? this.properties.numbers.maxValue : message.end,
|
|
559
|
+
start2: 0n,
|
|
560
|
+
end2: wrapped ? message.end : 0n,
|
|
561
|
+
});
|
|
419
562
|
|
|
420
563
|
if (!decoder) {
|
|
421
564
|
await this.simple.rpc.send(
|
|
@@ -620,10 +763,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
620
763
|
}
|
|
621
764
|
|
|
622
765
|
onEntryAdded(entry: Entry<any>): void {
|
|
766
|
+
this.invalidateLocalRangeEncoderCache();
|
|
623
767
|
return this.simple.onEntryAdded(entry);
|
|
624
768
|
}
|
|
625
769
|
|
|
626
770
|
onEntryRemoved(hash: string) {
|
|
771
|
+
this.invalidateLocalRangeEncoderCache();
|
|
627
772
|
return this.simple.onEntryRemoved(hash);
|
|
628
773
|
}
|
|
629
774
|
|
|
@@ -642,6 +787,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
642
787
|
for (const [, obj] of this.outgoingSyncProcesses) {
|
|
643
788
|
obj.free();
|
|
644
789
|
}
|
|
790
|
+
this.clearLocalRangeEncoderCache();
|
|
645
791
|
return this.simple.close();
|
|
646
792
|
}
|
|
647
793
|
|
package/src/sync/simple.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "../exchange-heads.js";
|
|
17
17
|
import { TransportMessage } from "../message.js";
|
|
18
18
|
import type { EntryReplicated } from "../ranges.js";
|
|
19
|
-
import type { SyncableKey, Syncronizer } from "./index.js";
|
|
19
|
+
import type { SyncableKey, SyncOptions, Syncronizer } from "./index.js";
|
|
20
20
|
|
|
21
21
|
@variant([0, 1])
|
|
22
22
|
export class RequestMaybeSync extends TransportMessage {
|
|
@@ -109,6 +109,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
109
109
|
log: Log<any>;
|
|
110
110
|
entryIndex: Index<EntryReplicated<R>, any>;
|
|
111
111
|
coordinateToHash: Cache<string>;
|
|
112
|
+
private syncOptions?: SyncOptions<R>;
|
|
112
113
|
|
|
113
114
|
// Syncing and dedeplucation work
|
|
114
115
|
syncMoreInterval?: ReturnType<typeof setTimeout>;
|
|
@@ -120,6 +121,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
120
121
|
entryIndex: Index<EntryReplicated<R>, any>;
|
|
121
122
|
log: Log<any>;
|
|
122
123
|
coordinateToHash: Cache<string>;
|
|
124
|
+
sync?: SyncOptions<R>;
|
|
123
125
|
}) {
|
|
124
126
|
this.syncInFlightQueue = new Map();
|
|
125
127
|
this.syncInFlightQueueInverted = new Map();
|
|
@@ -128,14 +130,35 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
128
130
|
this.log = properties.log;
|
|
129
131
|
this.entryIndex = properties.entryIndex;
|
|
130
132
|
this.coordinateToHash = properties.coordinateToHash;
|
|
133
|
+
this.syncOptions = properties.sync;
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
onMaybeMissingEntries(properties: {
|
|
134
137
|
entries: Map<string, EntryReplicated<R>>;
|
|
135
138
|
targets: string[];
|
|
136
139
|
}): Promise<void> {
|
|
140
|
+
let hashes: string[];
|
|
141
|
+
const priorityFn = this.syncOptions?.priority;
|
|
142
|
+
if (priorityFn) {
|
|
143
|
+
let index = 0;
|
|
144
|
+
const scored: { hash: string; index: number; priority: number }[] = [];
|
|
145
|
+
for (const [hash, entry] of properties.entries) {
|
|
146
|
+
const priorityValue = priorityFn(entry);
|
|
147
|
+
scored.push({
|
|
148
|
+
hash,
|
|
149
|
+
index,
|
|
150
|
+
priority: Number.isFinite(priorityValue) ? priorityValue : 0,
|
|
151
|
+
});
|
|
152
|
+
index += 1;
|
|
153
|
+
}
|
|
154
|
+
scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
|
|
155
|
+
hashes = scored.map((x) => x.hash);
|
|
156
|
+
} else {
|
|
157
|
+
hashes = [...properties.entries.keys()];
|
|
158
|
+
}
|
|
159
|
+
|
|
137
160
|
return this.rpc.send(
|
|
138
|
-
new RequestMaybeSync({ hashes
|
|
161
|
+
new RequestMaybeSync({ hashes }),
|
|
139
162
|
{
|
|
140
163
|
priority: 1,
|
|
141
164
|
mode: new SilentDelivery({ to: properties.targets, redundancy: 1 }),
|