@peerbit/shared-log 16.0.7 → 16.0.8

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.
@@ -91,6 +91,11 @@ const CODED_SYMBOL_WORDS = 3;
91
91
  const CODED_SYMBOL_WORD_BYTES = 8;
92
92
  const CODED_SYMBOL_BYTES = CODED_SYMBOL_WORDS * CODED_SYMBOL_WORD_BYTES;
93
93
  const MAX_CODED_SYMBOL_BATCH_SIZE = 1_024;
94
+ const DEFAULT_MAX_RATELESS_RECEIVE_RANGE_ENTRIES = 16_384;
95
+ const RATELESS_RANGE_QUERY_PAGE_SIZE = 1_024;
96
+ // Native range resolvers accept a u32 limit. Keep one value available as the
97
+ // overflow sentinel used to distinguish an exact fit from a larger range.
98
+ const MAX_RATELESS_RANGE_ENTRIES = 0xffff_fffe;
94
99
  const BIG_UINT64_ARRAY_IS_LITTLE_ENDIAN =
95
100
  typeof BigUint64Array !== "undefined" &&
96
101
  new Uint8Array(new BigUint64Array([1n]).buffer)[0] === 1;
@@ -656,6 +661,7 @@ const buildEncoderOrDecoderFromRange = async <
656
661
  },
657
662
  entryIndex: Index<EntryReplicated<D>>,
658
663
  type: T,
664
+ maxEntries: number,
659
665
  profile?: SyncProfileFn,
660
666
  resolveHashNumbersInRange?: HashSymbolRangeResolver,
661
667
  ): Promise<E | false> => {
@@ -665,6 +671,7 @@ const buildEncoderOrDecoderFromRange = async <
665
671
  let transferred = false;
666
672
  try {
667
673
  const rangeQueryStartedAt = syncProfileStart(profile);
674
+ const queryLimit = maxEntries + 1;
668
675
  let hashNumbers: Array<bigint | number> | BigUint64Array | undefined;
669
676
  let source = "index";
670
677
  const resolved = await resolveHashNumbersInRange?.({
@@ -672,45 +679,87 @@ const buildEncoderOrDecoderFromRange = async <
672
679
  start1: ranges.start1,
673
680
  end2: ranges.end2,
674
681
  start2: ranges.start2,
682
+ limit: queryLimit,
675
683
  });
676
684
  if (resolved) {
677
685
  source = "native";
678
- hashNumbers =
686
+ if (
679
687
  typeof BigUint64Array !== "undefined" &&
680
688
  resolved instanceof BigUint64Array
681
- ? resolved
682
- : [...resolved];
689
+ ) {
690
+ hashNumbers = resolved;
691
+ } else {
692
+ hashNumbers = [];
693
+ const iterator = resolved[Symbol.iterator]();
694
+ let exhausted = false;
695
+ try {
696
+ while (hashNumbers.length < queryLimit) {
697
+ const next = iterator.next();
698
+ if (next.done) {
699
+ exhausted = true;
700
+ break;
701
+ }
702
+ hashNumbers.push(next.value);
703
+ }
704
+ } finally {
705
+ if (!exhausted) {
706
+ iterator.return?.();
707
+ }
708
+ }
709
+ }
683
710
  } else {
684
- const entries = await entryIndex
685
- .iterate(
686
- {
687
- // Range sync for IBLT is done in hashNumber space.
688
- query: matchEntriesByHashNumberInRangeQuery({
689
- end1: ranges.end1,
690
- start1: ranges.start1,
691
- end2: ranges.end2,
692
- start2: ranges.start2,
693
- }),
694
- },
695
- {
696
- shape: {
697
- hash: true,
698
- hashNumber: true,
699
- },
711
+ const iterator = entryIndex.iterate(
712
+ {
713
+ consistency: "weak",
714
+ // Range sync for IBLT is done in hashNumber space.
715
+ query: matchEntriesByHashNumberInRangeQuery({
716
+ end1: ranges.end1,
717
+ start1: ranges.start1,
718
+ end2: ranges.end2,
719
+ start2: ranges.start2,
720
+ }),
721
+ },
722
+ {
723
+ shape: {
724
+ hash: true,
725
+ hashNumber: true,
700
726
  },
701
- )
702
- .all();
703
- hashNumbers = entries.map((entry) => entry.value.hashNumber);
727
+ },
728
+ );
729
+ hashNumbers = [];
730
+ try {
731
+ while (hashNumbers.length < queryLimit) {
732
+ const remaining = queryLimit - hashNumbers.length;
733
+ const entries = await iterator.next(
734
+ Math.min(RATELESS_RANGE_QUERY_PAGE_SIZE, remaining),
735
+ );
736
+ if (entries.length === 0) {
737
+ break;
738
+ }
739
+ for (const entry of entries) {
740
+ if (hashNumbers.length >= queryLimit) {
741
+ break;
742
+ }
743
+ hashNumbers.push(entry.value.hashNumber);
744
+ }
745
+ if (iterator.done()) {
746
+ break;
747
+ }
748
+ }
749
+ } finally {
750
+ await iterator.close();
751
+ }
704
752
  }
753
+ const overflow = hashNumbers.length > maxEntries;
705
754
  if (profile) {
706
755
  emitSyncProfileDuration(profile, rangeQueryStartedAt, {
707
756
  name: "rateless.rangeQuery",
708
757
  entries: hashNumbers.length,
709
- details: { type, source },
758
+ details: { type, source, overflow, maxEntries },
710
759
  });
711
760
  }
712
761
 
713
- if (hashNumbers.length === 0) {
762
+ if (hashNumbers.length === 0 || overflow) {
714
763
  return false;
715
764
  }
716
765
 
@@ -912,6 +961,13 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
912
961
  : DEFAULT_MAX_CONVERGENT_TRACKED_HASHES;
913
962
  }
914
963
 
964
+ private get maxRatelessReceiveRangeEntries() {
965
+ const value = this.properties.sync?.maxRatelessReceiveRangeEntries;
966
+ return value && Number.isFinite(value) && value > 0
967
+ ? Math.min(MAX_RATELESS_RANGE_ENTRIES, Math.max(1, Math.floor(value)))
968
+ : DEFAULT_MAX_RATELESS_RECEIVE_RANGE_ENTRIES;
969
+ }
970
+
915
971
  private normalizeRetryIntervals(retryIntervalsMs?: number[]): number[] {
916
972
  if (!retryIntervalsMs || retryIntervalsMs.length === 0) {
917
973
  return [...DEFAULT_CONVERGENT_RETRY_INTERVALS_MS];
@@ -1342,6 +1398,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1342
1398
  ranges,
1343
1399
  this.properties.entryIndex,
1344
1400
  "encoder",
1401
+ this.maxRatelessReceiveRangeEntries,
1345
1402
  profile,
1346
1403
  this.properties.resolveHashNumbersInRange,
1347
1404
  )) as EncoderWrapper | false;
@@ -139,6 +139,8 @@ type KnownSyncKeys = {
139
139
  checkedHashes: boolean;
140
140
  };
141
141
 
142
+ const SIMPLE_COORDINATE_INDEX_PAGE_SIZE = 1_024;
143
+
142
144
  const getHashesFromSymbols = async (
143
145
  symbols: bigint[],
144
146
  entryIndex: Index<EntryReplicated<any>, any>,
@@ -151,6 +153,7 @@ const getHashesFromSymbols = async (
151
153
  let batchSize = 128; // TODO arg
152
154
  let results = new Set<string>();
153
155
  let missingSymbols: bigint[] = [];
156
+ let remainingIndexCandidates = maxHashes;
154
157
  const addHash = (hash: string) => {
155
158
  if (results.has(hash)) {
156
159
  return true;
@@ -171,22 +174,50 @@ const getHashesFromSymbols = async (
171
174
  };
172
175
  const handleBatch = async (end = false) => {
173
176
  if (queries.length >= batchSize || (end && queries.length > 0)) {
174
- const entries = await entryIndex
175
- .iterate(
176
- { query: queries.length > 1 ? new Or(queries) : queries },
177
- { shape: { hash: true, hashNumber: true } },
178
- )
179
- .all();
177
+ const remainingHashes = maxHashes - results.size;
178
+ if (remainingHashes <= 0 || remainingIndexCandidates <= 0) {
179
+ queries = [];
180
+ return;
181
+ }
182
+ const iterator = entryIndex.iterate(
183
+ {
184
+ consistency: "weak",
185
+ query: queries.length > 1 ? new Or(queries) : queries,
186
+ },
187
+ { shape: { hash: true, hashNumber: true } },
188
+ );
180
189
  queries = [];
181
-
182
- for (const entry of entries) {
183
- if (!addHash(entry.value.hash)) {
184
- break;
185
- }
186
- coordinateToHash.add(entry.value.hashNumber, entry.value.hash);
187
- if (results.size >= maxHashes) {
188
- break;
190
+ try {
191
+ while (results.size < maxHashes && remainingIndexCandidates > 0) {
192
+ const entries = await iterator.next(
193
+ Math.min(
194
+ SIMPLE_COORDINATE_INDEX_PAGE_SIZE,
195
+ maxHashes - results.size,
196
+ remainingIndexCandidates,
197
+ ),
198
+ );
199
+ if (entries.length === 0) {
200
+ break;
201
+ }
202
+ for (const entry of entries) {
203
+ if (remainingIndexCandidates <= 0) {
204
+ break;
205
+ }
206
+ remainingIndexCandidates -= 1;
207
+ if (!addHash(entry.value.hash)) {
208
+ break;
209
+ }
210
+ coordinateToHash.add(entry.value.hashNumber, entry.value.hash);
211
+ if (results.size >= maxHashes) {
212
+ break;
213
+ }
214
+ }
215
+ if (iterator.done()) {
216
+ break;
217
+ }
189
218
  }
219
+ } finally {
220
+ await iterator.close();
190
221
  }
191
222
  }
192
223
  };