@peerbit/shared-log 7.0.10 → 7.0.11

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/src/index.ts CHANGED
@@ -51,7 +51,6 @@ import yallist from "yallist";
51
51
  import {
52
52
  AcknowledgeDelivery,
53
53
  DeliveryMode,
54
- SeekDelivery,
55
54
  SilentDelivery,
56
55
  NotStartedError
57
56
  } from "@peerbit/stream-interface";
@@ -62,7 +61,7 @@ import { PIDReplicationController } from "./pid.js";
62
61
  export * from "./replication.js";
63
62
  import PQueue from "p-queue";
64
63
  import { CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
65
- import { collectNodesAroundPoint, getCover } from "./ranges.js";
64
+ import { getCoverSet, getSamples, isMatured } from "./ranges.js";
66
65
  export { type CPUUsage, CPUUsageIntervalLag };
67
66
  export { Observer, Replicator, Role };
68
67
 
@@ -204,6 +203,7 @@ export class SharedLog<T = Uint8Array> extends Program<
204
203
  private remoteBlocks: RemoteBlocks;
205
204
 
206
205
  private openTime: number;
206
+ private oldestOpenTime: number;
207
207
 
208
208
  private sync?: (entry: Entry<T>) => boolean;
209
209
 
@@ -219,6 +219,7 @@ export class SharedLog<T = Uint8Array> extends Program<
219
219
  private syncMoreInterval?: ReturnType<typeof setTimeout>;
220
220
  private syncInFlightQueue: Map<string, PublicSignKey[]>;
221
221
  private syncInFlightQueueInverted: Map<string, Set<string>>;
222
+ private syncInFlight: Map<string, Map<string, { timestamp: number }>>;
222
223
 
223
224
  replicas: ReplicationLimits;
224
225
 
@@ -301,7 +302,7 @@ export class SharedLog<T = Uint8Array> extends Program<
301
302
  } else {
302
303
  this._roleOptions = new Replicator({
303
304
  factor: options.factor,
304
- offset: hashToUniformNumber(this.node.identity.publicKey.bytes)
305
+ offset: this.getReplicationOffset()
305
306
  });
306
307
  }
307
308
  } else {
@@ -326,12 +327,12 @@ export class SharedLog<T = Uint8Array> extends Program<
326
327
  if (this._roleOptions?.limits) {
327
328
  this._role = new Replicator({
328
329
  factor: this._role instanceof Replicator ? this._role.factor : 0,
329
- offset: hashToUniformNumber(this.node.identity.publicKey.bytes)
330
+ offset: this.getReplicationOffset()
330
331
  });
331
332
  } else {
332
333
  this._role = new Replicator({
333
334
  factor: this._role instanceof Replicator ? this._role.factor : 1,
334
- offset: hashToUniformNumber(this.node.identity.publicKey.bytes)
335
+ offset: this.getReplicationOffset()
335
336
  });
336
337
  }
337
338
  }
@@ -354,12 +355,7 @@ export class SharedLog<T = Uint8Array> extends Program<
354
355
  );
355
356
 
356
357
  await this.rpc.subscribe();
357
-
358
- await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
359
- mode: new SeekDelivery({
360
- redundancy: 1
361
- })
362
- });
358
+ await this.rpc.send(new ResponseRoleMessage({ role: this._role }));
363
359
 
364
360
  if (onRoleChange && changed !== "none") {
365
361
  this.onRoleChange(this._role, this.node.identity.publicKey);
@@ -418,6 +414,15 @@ export class SharedLog<T = Uint8Array> extends Program<
418
414
  }
419
415
  leaders = newAndOldLeaders;
420
416
  }
417
+ let set = this._gidPeersHistory.get(result.entry.meta.gid);
418
+ if (!set) {
419
+ set = new Set(leaders);
420
+ this._gidPeersHistory.set(result.entry.meta.gid, set);
421
+ } else {
422
+ for (const receiver of leaders) {
423
+ set.add(receiver);
424
+ }
425
+ }
421
426
  mode = isLeader
422
427
  ? new SilentDelivery({ redundancy: 1, to: leaders })
423
428
  : new AcknowledgeDelivery({ redundancy: 1, to: leaders });
@@ -453,7 +458,9 @@ export class SharedLog<T = Uint8Array> extends Program<
453
458
  this.latestRoleMessages = new Map();
454
459
  this.syncInFlightQueue = new Map();
455
460
  this.syncInFlightQueueInverted = new Map();
461
+ this.syncInFlight = new Map();
456
462
  this.openTime = +new Date();
463
+ this.oldestOpenTime = this.openTime;
457
464
  this.timeUntilRoleMaturity =
458
465
  options?.timeUntilRoleMaturity || WAIT_FOR_ROLE_MATURITY;
459
466
  this.waitForReplicatorTimeout =
@@ -590,21 +597,25 @@ export class SharedLog<T = Uint8Array> extends Program<
590
597
  this.syncInFlightQueue.delete(key);
591
598
  }
592
599
  }
593
- this.rpc
594
- .send(
595
- new ResponseMaybeSync({
596
- hashes: requestHashes
597
- }),
598
- {
599
- mode: new SilentDelivery({ to: from, redundancy: 1 })
600
- }
601
- )
602
- .finally(() => {
603
- if (this.closed) {
604
- return;
600
+
601
+ const nowMin10s = +new Date() - 1e4;
602
+ for (const [key, map] of this.syncInFlight) {
603
+ // cleanup "old" missing syncs
604
+ for (const [hash, { timestamp }] of map) {
605
+ if (timestamp < nowMin10s) {
606
+ map.delete(hash);
605
607
  }
606
- this.syncMoreInterval = setTimeout(requestSync, 1e4);
607
- });
608
+ }
609
+ if (map.size === 0) {
610
+ this.syncInFlight.delete(key);
611
+ }
612
+ }
613
+ this.requestSync(requestHashes, from).finally(() => {
614
+ if (this.closed) {
615
+ return;
616
+ }
617
+ this.syncMoreInterval = setTimeout(requestSync, 1e4);
618
+ });
608
619
  };
609
620
  requestSync();
610
621
  }
@@ -668,6 +679,7 @@ export class SharedLog<T = Uint8Array> extends Program<
668
679
  this._pendingIHave.clear();
669
680
  this.syncInFlightQueue.clear();
670
681
  this.syncInFlightQueueInverted.clear();
682
+ this.syncInFlight.clear();
671
683
  this.latestRoleMessages.clear();
672
684
  this._gidPeersHistory.clear();
673
685
 
@@ -824,6 +836,17 @@ export class SharedLog<T = Uint8Array> extends Program<
824
836
  this.rebalanceParticipationDebounced?.();
825
837
  }
826
838
 
839
+ /// we clear sync in flight here because we want to join before that, so that entries are totally accounted for
840
+ for (const head of heads) {
841
+ const set = this.syncInFlight.get(context.from.hashcode());
842
+ if (set) {
843
+ set.delete(head.entry.hash);
844
+ if (set?.size === 0) {
845
+ this.syncInFlight.delete(context.from.hashcode());
846
+ }
847
+ }
848
+ }
849
+
827
850
  if (maybeDelete) {
828
851
  for (const entries of maybeDelete as EntryWithRefs<any>[][]) {
829
852
  const headsWithGid = this.log.headsIndex.gids.get(
@@ -930,18 +953,11 @@ export class SharedLog<T = Uint8Array> extends Program<
930
953
  inverted.add(hash);
931
954
  } else if (!this.log.has(hash)) {
932
955
  this.syncInFlightQueue.set(hash, []);
933
- requestHashes.push(hash);
956
+ requestHashes.push(hash); // request immediately (first time we have seen this hash)
934
957
  }
935
958
  }
936
959
 
937
- await this.rpc.send(
938
- new ResponseMaybeSync({
939
- hashes: requestHashes
940
- }),
941
- {
942
- mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
943
- }
944
- );
960
+ await this.requestSync(requestHashes, [context.from.hashcode()]);
945
961
  } else if (msg instanceof ResponseMaybeSync) {
946
962
  // TODO better choice of step size
947
963
  const entries = (
@@ -1042,10 +1058,12 @@ export class SharedLog<T = Uint8Array> extends Program<
1042
1058
  async waitForReplicator(...keys: PublicSignKey[]) {
1043
1059
  const check = () => {
1044
1060
  for (const k of keys) {
1061
+ const rect = this.getReplicatorsSorted()
1062
+ ?.toArray()
1063
+ ?.find((x) => x.publicKey.equals(k));
1045
1064
  if (
1046
- !this.getReplicatorsSorted()
1047
- ?.toArray()
1048
- ?.find((x) => x.publicKey.equals(k))
1065
+ !rect ||
1066
+ !isMatured(rect.role, +new Date(), this.getDefaultMinRoleAge())
1049
1067
  ) {
1050
1068
  return false;
1051
1069
  }
@@ -1069,6 +1087,10 @@ export class SharedLog<T = Uint8Array> extends Program<
1069
1087
  return !!isLeader;
1070
1088
  }
1071
1089
 
1090
+ private getReplicationOffset() {
1091
+ return hashToUniformNumber(this.node.identity.publicKey.bytes);
1092
+ }
1093
+
1072
1094
  private async waitForIsLeader(
1073
1095
  slot: { toString(): string },
1074
1096
  numberOfLeaders: number,
@@ -1137,6 +1159,17 @@ export class SharedLog<T = Uint8Array> extends Program<
1137
1159
  return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
1138
1160
  }
1139
1161
 
1162
+ getDefaultMinRoleAge(): number {
1163
+ const now = +new Date();
1164
+ const replLength = this.getReplicatorsSorted()!.length;
1165
+ const diffToOldest =
1166
+ replLength > 1 ? now - this.oldestOpenTime : Number.MAX_SAFE_INTEGER;
1167
+ return Math.min(
1168
+ this.timeUntilRoleMaturity,
1169
+ diffToOldest,
1170
+ (this.timeUntilRoleMaturity * Math.log(replLength)) / 3
1171
+ ); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
1172
+ }
1140
1173
  private findLeadersFromUniformNumber(
1141
1174
  cursor: number,
1142
1175
  numberOfLeaders: number,
@@ -1144,33 +1177,26 @@ export class SharedLog<T = Uint8Array> extends Program<
1144
1177
  roleAge?: number;
1145
1178
  }
1146
1179
  ) {
1147
- const leaders: Set<string> = new Set();
1148
- const width = 1;
1149
- const peers = this.getReplicatorsSorted();
1150
- if (!peers || peers?.length === 0) {
1151
- return [];
1152
- }
1153
- numberOfLeaders = Math.min(numberOfLeaders, peers.length);
1180
+ const roleAge = options?.roleAge ?? this.getDefaultMinRoleAge(); // TODO -500 as is added so that i f someone else is just as new as us, then we treat them as mature as us. without -500 we might be slower syncing if two nodes starts almost at the same time
1154
1181
 
1155
- const t = +new Date();
1156
- const roleAge =
1157
- options?.roleAge ??
1158
- Math.min(this.timeUntilRoleMaturity, +new Date() - this.openTime - 500); // TODO -500 as is added so that i f someone else is just as new as us, then we treat them as mature as us. without -500 we might be slower syncing if two nodes starts almost at the same time
1159
-
1160
- for (let i = 0; i < numberOfLeaders; i++) {
1161
- const point = ((cursor + i / numberOfLeaders) % 1) * width;
1162
- const currentNode = peers.head;
1163
- collectNodesAroundPoint(t, roleAge, peers, currentNode, leaders, point);
1164
- }
1182
+ const sampes = getSamples(
1183
+ cursor,
1184
+ this.getReplicatorsSorted()!,
1185
+ numberOfLeaders,
1186
+ roleAge,
1187
+ this.role instanceof Replicator && this.role.factor === 0
1188
+ ? this.node.identity.publicKey.hashcode()
1189
+ : undefined
1190
+ );
1165
1191
 
1166
- return [...leaders];
1192
+ return sampes;
1167
1193
  }
1168
1194
 
1169
1195
  /**
1170
1196
  *
1171
1197
  * @returns groups where at least one in any group will have the entry you are looking for
1172
1198
  */
1173
- getReplicatorUnion(roleAge: number = this.timeUntilRoleMaturity) {
1199
+ getReplicatorUnion(roleAge: number = this.getDefaultMinRoleAge()) {
1174
1200
  if (this.closed === true) {
1175
1201
  throw new Error("Closed");
1176
1202
  }
@@ -1192,12 +1218,18 @@ export class SharedLog<T = Uint8Array> extends Program<
1192
1218
  // the entry we are looking for
1193
1219
  const coveringWidth = width / minReplicas;
1194
1220
 
1195
- return getCover(
1221
+ const set = getCoverSet(
1196
1222
  coveringWidth,
1197
1223
  peers,
1198
1224
  roleAge,
1199
1225
  this.role instanceof Replicator ? this.node.identity.publicKey : undefined
1200
1226
  );
1227
+
1228
+ // add all in flight
1229
+ for (const [key, _] of this.syncInFlight) {
1230
+ set.add(key);
1231
+ }
1232
+ return [...set];
1201
1233
  }
1202
1234
 
1203
1235
  async replicator(
@@ -1255,7 +1287,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1255
1287
  await this.rebalanceParticipationDebounced?.(); /* await this.rebalanceParticipation(false); */
1256
1288
  if (update.changed === "added") {
1257
1289
  await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
1258
- mode: new SeekDelivery({
1290
+ mode: new SilentDelivery({
1259
1291
  to: [publicKey.hashcode()],
1260
1292
  redundancy: 1
1261
1293
  })
@@ -1303,6 +1335,10 @@ export class SharedLog<T = Uint8Array> extends Program<
1303
1335
  // TODO should we remove replicators if they are already added?
1304
1336
  return { changed: "none" };
1305
1337
  }
1338
+ this.oldestOpenTime = Math.min(
1339
+ this.oldestOpenTime,
1340
+ Number(role.timestamp)
1341
+ );
1306
1342
 
1307
1343
  // insert or if already there do nothing
1308
1344
  const rect: ReplicatorRect = {
@@ -1380,13 +1416,29 @@ export class SharedLog<T = Uint8Array> extends Program<
1380
1416
  for (const [_a, b] of this._gidPeersHistory) {
1381
1417
  b.delete(publicKey.hashcode());
1382
1418
  }
1419
+ this.syncInFlight.delete(publicKey.hashcode());
1420
+ const waitingHashes = this.syncInFlightQueueInverted.get(
1421
+ publicKey.hashcode()
1422
+ );
1423
+ if (waitingHashes) {
1424
+ for (const hash of waitingHashes) {
1425
+ let arr = this.syncInFlightQueue.get(hash);
1426
+ if (arr) {
1427
+ arr = arr.filter((x) => !x.equals(publicKey));
1428
+ }
1429
+ if (this.syncInFlightQueue.size === 0) {
1430
+ this.syncInFlightQueue.delete(hash);
1431
+ }
1432
+ }
1433
+ }
1434
+ this.syncInFlightQueueInverted.delete(publicKey.hashcode());
1383
1435
  }
1384
1436
 
1385
1437
  if (subscribed) {
1386
1438
  if (this.role instanceof Replicator) {
1387
1439
  this.rpc
1388
1440
  .send(new ResponseRoleMessage({ role: this._role }), {
1389
- mode: new SeekDelivery({ redundancy: 1, to: [publicKey] })
1441
+ mode: new SilentDelivery({ redundancy: 1, to: [publicKey] })
1390
1442
  })
1391
1443
  .catch((e) => logger.error(e.toString()));
1392
1444
  }
@@ -1637,6 +1689,29 @@ export class SharedLog<T = Uint8Array> extends Program<
1637
1689
  return changed;
1638
1690
  }
1639
1691
 
1692
+ private async requestSync(hashes: string[], to: Set<string> | string[]) {
1693
+ const now = +new Date();
1694
+ for (const node of to) {
1695
+ let map = this.syncInFlight.get(node);
1696
+ if (!map) {
1697
+ map = new Map();
1698
+ this.syncInFlight.set(node, map);
1699
+ }
1700
+ for (const hash of hashes) {
1701
+ map.set(hash, { timestamp: now });
1702
+ }
1703
+ }
1704
+
1705
+ await this.rpc.send(
1706
+ new ResponseMaybeSync({
1707
+ hashes: hashes
1708
+ }),
1709
+ {
1710
+ mode: new SilentDelivery({ to, redundancy: 1 })
1711
+ }
1712
+ );
1713
+ }
1714
+
1640
1715
  async _onUnsubscription(evt: CustomEvent<UnsubcriptionEvent>) {
1641
1716
  logger.debug(
1642
1717
  `Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(