@peerbit/shared-log 12.2.0-369b236 → 12.2.0-62829ef

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.
@@ -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 { SyncableKey, Syncronizer } from "./index.js";
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,17 +337,57 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
251
337
  return;
252
338
  }
253
339
 
254
- await ribltReady;
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
- allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
345
+ nonBoundaryEntries.push(entry);
262
346
  }
263
347
  }
264
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 = nonBoundaryEntries
388
+ .filter((entry) => !entriesToSyncNaively.has(entry.hash))
389
+ .map((entry) => coerceBigInt(entry.hashNumber));
390
+
265
391
  if (entriesToSyncNaively.size > 0) {
266
392
  // If there are special-case entries, sync them simply in parallel
267
393
  await this.simple.onMaybeMissingEntries({
@@ -284,6 +410,8 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
284
410
  return;
285
411
  }
286
412
 
413
+ await ribltReady;
414
+
287
415
  const sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
288
416
  if (a > b) {
289
417
  return 1;
@@ -406,16 +534,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
406
534
  this.startedOrCompletedSynchronizations.add(syncId);
407
535
 
408
536
  const wrapped = message.end < message.start;
409
- const decoder = await buildEncoderOrDecoderFromRange(
410
- {
411
- start1: message.start,
412
- end1: wrapped ? this.properties.numbers.maxValue : message.end,
413
- start2: 0n,
414
- end2: wrapped ? message.end : 0n,
415
- },
416
- this.properties.entryIndex,
417
- "decoder",
418
- );
537
+ const decoder = await this.getLocalDecoderForRange({
538
+ start1: message.start,
539
+ end1: wrapped ? this.properties.numbers.maxValue : message.end,
540
+ start2: 0n,
541
+ end2: wrapped ? message.end : 0n,
542
+ });
419
543
 
420
544
  if (!decoder) {
421
545
  await this.simple.rpc.send(
@@ -620,10 +744,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
620
744
  }
621
745
 
622
746
  onEntryAdded(entry: Entry<any>): void {
747
+ this.invalidateLocalRangeEncoderCache();
623
748
  return this.simple.onEntryAdded(entry);
624
749
  }
625
750
 
626
751
  onEntryRemoved(hash: string) {
752
+ this.invalidateLocalRangeEncoderCache();
627
753
  return this.simple.onEntryRemoved(hash);
628
754
  }
629
755
 
@@ -642,6 +768,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
642
768
  for (const [, obj] of this.outgoingSyncProcesses) {
643
769
  obj.free();
644
770
  }
771
+ this.clearLocalRangeEncoderCache();
645
772
  return this.simple.close();
646
773
  }
647
774
 
@@ -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: [...properties.entries.keys()] }),
161
+ new RequestMaybeSync({ hashes }),
139
162
  {
140
163
  priority: 1,
141
164
  mode: new SilentDelivery({ to: properties.targets, redundancy: 1 }),