@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/lib/esm/index.d.ts +5 -0
- package/lib/esm/index.js +106 -45
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/ranges.d.ts +8 -1
- package/lib/esm/ranges.js +145 -67
- package/lib/esm/ranges.js.map +1 -1
- package/lib/esm/role.d.ts +1 -4
- package/lib/esm/role.js +1 -17
- package/lib/esm/role.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +134 -59
- package/src/ranges.ts +201 -89
- package/src/role.ts +2 -24
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 {
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
{
|
|
599
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
!
|
|
1047
|
-
|
|
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
|
|
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
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|