@peerbit/stream 2.0.4 → 2.0.5
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 +4 -1
- package/lib/esm/index.js +31 -31
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/routes.d.ts +20 -27
- package/lib/esm/routes.js +103 -86
- package/lib/esm/routes.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +42 -34
- package/src/routes.ts +131 -127
package/src/routes.ts
CHANGED
|
@@ -1,36 +1,68 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbortError, delay } from "@peerbit/time";
|
|
2
2
|
|
|
3
3
|
export const MAX_ROUTE_DISTANCE = Number.MAX_SAFE_INTEGER - 1;
|
|
4
|
+
type RouteInfo = {
|
|
5
|
+
session: number;
|
|
6
|
+
hash: string;
|
|
7
|
+
expireAt?: number;
|
|
8
|
+
distance: number;
|
|
9
|
+
};
|
|
4
10
|
export class Routes {
|
|
5
11
|
// END receiver -> Neighbour
|
|
6
12
|
|
|
7
13
|
routes: Map<
|
|
8
14
|
string,
|
|
9
|
-
Map<string, { session: number; list: { hash: string; distance: number }[] }>
|
|
10
|
-
> = new Map();
|
|
11
|
-
|
|
12
|
-
pendingRoutes: Map<
|
|
13
|
-
number,
|
|
14
15
|
Map<
|
|
15
16
|
string,
|
|
16
17
|
{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}[]
|
|
18
|
+
latestSession: number;
|
|
19
|
+
list: RouteInfo[];
|
|
20
|
+
}
|
|
21
21
|
>
|
|
22
22
|
> = new Map();
|
|
23
|
-
latestSession: number;
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
routeMaxRetentionPeriod: number;
|
|
25
|
+
signal: AbortSignal;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
readonly me: string,
|
|
29
|
+
options: { routeMaxRetentionPeriod: number; signal: AbortSignal }
|
|
30
|
+
) {
|
|
31
|
+
this.routeMaxRetentionPeriod = options.routeMaxRetentionPeriod;
|
|
32
|
+
this.signal = options.signal;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
clear() {
|
|
30
36
|
this.routes.clear();
|
|
31
|
-
this.pendingRoutes.clear();
|
|
32
37
|
}
|
|
33
38
|
|
|
39
|
+
private cleanup(from: string, to: string) {
|
|
40
|
+
const fromMap = this.routes.get(from);
|
|
41
|
+
if (fromMap) {
|
|
42
|
+
const map = fromMap.get(to);
|
|
43
|
+
if (map) {
|
|
44
|
+
const now = +new Date();
|
|
45
|
+
const keepRoutes: RouteInfo[] = [];
|
|
46
|
+
for (const route of map.list) {
|
|
47
|
+
// delete all routes after a while
|
|
48
|
+
if (route.expireAt != null && route.expireAt < now) {
|
|
49
|
+
// expired
|
|
50
|
+
} else {
|
|
51
|
+
keepRoutes.push(route);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (keepRoutes.length > 0) {
|
|
56
|
+
map.list = keepRoutes;
|
|
57
|
+
} else {
|
|
58
|
+
fromMap.delete(to);
|
|
59
|
+
if (fromMap.size === 1) {
|
|
60
|
+
this.routes.delete(from);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
34
66
|
add(
|
|
35
67
|
from: string,
|
|
36
68
|
neighbour: string,
|
|
@@ -44,20 +76,11 @@ export class Routes {
|
|
|
44
76
|
this.routes.set(from, fromMap);
|
|
45
77
|
}
|
|
46
78
|
|
|
47
|
-
let prev = fromMap.get(target)
|
|
48
|
-
session: session ?? +new Date(),
|
|
49
|
-
list: [] as { hash: string; distance: number }[]
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
this.latestSession = Math.max(this.latestSession, session);
|
|
79
|
+
let prev = fromMap.get(target);
|
|
53
80
|
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
prev = { session, list: [] }; // reset route info how to reach this target
|
|
58
|
-
} else if (prev.session > session) {
|
|
59
|
-
return; // new routing information superseedes this
|
|
60
|
-
}
|
|
81
|
+
if (!prev) {
|
|
82
|
+
prev = { latestSession: 0, list: [] as RouteInfo[] };
|
|
83
|
+
fromMap.set(target, prev);
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
if (from === this.me && neighbour === target) {
|
|
@@ -66,16 +89,60 @@ export class Routes {
|
|
|
66
89
|
distance = -1;
|
|
67
90
|
}
|
|
68
91
|
|
|
92
|
+
// Update routes and cleanup all old routes that are older than latest session - some threshold
|
|
93
|
+
const isNewSession = session > prev.latestSession;
|
|
94
|
+
prev.latestSession = Math.max(session, prev.latestSession);
|
|
95
|
+
|
|
96
|
+
if (isNewSession) {
|
|
97
|
+
// Mark previous routes as old
|
|
98
|
+
|
|
99
|
+
const expireAt = +new Date() + this.routeMaxRetentionPeriod;
|
|
100
|
+
for (const route of prev.list) {
|
|
101
|
+
// delete all routes after a while
|
|
102
|
+
if (!route.expireAt) {
|
|
103
|
+
route.expireAt = expireAt;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Initiate cleanup
|
|
108
|
+
if (distance !== -1) {
|
|
109
|
+
delay(this.routeMaxRetentionPeriod + 100, { signal: this.signal })
|
|
110
|
+
.then(() => {
|
|
111
|
+
this.cleanup(from, target);
|
|
112
|
+
})
|
|
113
|
+
.catch((e) => {
|
|
114
|
+
if (e instanceof AbortError) {
|
|
115
|
+
// skip
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
throw e;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Modify list for new/update route
|
|
69
124
|
for (const route of prev.list) {
|
|
70
125
|
if (route.hash === neighbour) {
|
|
71
|
-
route
|
|
72
|
-
|
|
73
|
-
|
|
126
|
+
// if route is faster or just as fast, update existing route
|
|
127
|
+
if (route.distance > distance) {
|
|
128
|
+
route.distance = distance;
|
|
129
|
+
route.session = session;
|
|
130
|
+
route.expireAt = undefined; // remove expiry since we updated
|
|
131
|
+
prev.list.sort((a, b) => a.distance - b.distance);
|
|
132
|
+
return;
|
|
133
|
+
} else if (route.distance === distance) {
|
|
134
|
+
route.session = session;
|
|
135
|
+
route.expireAt = undefined; // remove expiry since we updated
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// else break and push the route as a new route (that ought to be longer)
|
|
140
|
+
break;
|
|
74
141
|
}
|
|
75
142
|
}
|
|
76
|
-
|
|
143
|
+
|
|
144
|
+
prev.list.push({ distance, session, hash: neighbour });
|
|
77
145
|
prev.list.sort((a, b) => a.distance - b.distance);
|
|
78
|
-
fromMap.set(target, prev);
|
|
79
146
|
}
|
|
80
147
|
|
|
81
148
|
removeTarget(target: string) {
|
|
@@ -177,54 +244,62 @@ export class Routes {
|
|
|
177
244
|
|
|
178
245
|
// for all tos if
|
|
179
246
|
getFanout(
|
|
180
|
-
from:
|
|
247
|
+
from: string,
|
|
181
248
|
tos: string[],
|
|
182
249
|
redundancy: number
|
|
183
|
-
): Map<string, { to: string; timestamp: number }
|
|
250
|
+
): Map<string, Map<string, { to: string; timestamp: number }>> | undefined {
|
|
184
251
|
if (tos.length === 0) {
|
|
185
252
|
return undefined;
|
|
186
253
|
}
|
|
187
254
|
|
|
188
255
|
let fanoutMap:
|
|
189
|
-
| Map<string, { to: string; timestamp: number }
|
|
256
|
+
| Map<string, Map<string, { to: string; timestamp: number }>>
|
|
190
257
|
| undefined = undefined;
|
|
191
258
|
|
|
192
|
-
const fromKey = from.hashcode();
|
|
193
|
-
|
|
194
259
|
// Message to > 0
|
|
195
260
|
if (tos.length > 0) {
|
|
196
261
|
for (const to of tos) {
|
|
197
|
-
if (to === this.me ||
|
|
262
|
+
if (to === this.me || from === to) {
|
|
198
263
|
continue; // don't send to me or backwards
|
|
199
264
|
}
|
|
200
265
|
|
|
201
|
-
const neighbour = this.findNeighbor(
|
|
266
|
+
const neighbour = this.findNeighbor(from, to);
|
|
202
267
|
if (neighbour) {
|
|
203
268
|
let foundClosest = false;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
) {
|
|
209
|
-
const distance = neighbour.list[i].distance;
|
|
210
|
-
if (distance >= redundancy) {
|
|
269
|
+
let redundancyModified = redundancy;
|
|
270
|
+
for (let i = 0; i < neighbour.list.length; i++) {
|
|
271
|
+
const { distance, session } = neighbour.list[i];
|
|
272
|
+
if (distance >= redundancyModified) {
|
|
211
273
|
break; // because neighbour listis sorted
|
|
212
274
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
const fanout: { to: string; timestamp: number }[] = (
|
|
275
|
+
|
|
276
|
+
let fanout: Map<string, { to: string; timestamp: number }> = (
|
|
217
277
|
fanoutMap || (fanoutMap = new Map())
|
|
218
278
|
).get(neighbour.list[i].hash);
|
|
219
279
|
if (!fanout) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
280
|
+
fanout = new Map();
|
|
281
|
+
fanoutMap.set(neighbour.list[i].hash, fanout);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
fanout.set(to, { to, timestamp: session });
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
(distance == 0 && session === neighbour.latestSession) ||
|
|
288
|
+
distance == -1
|
|
289
|
+
) {
|
|
290
|
+
foundClosest = true;
|
|
291
|
+
|
|
292
|
+
if (distance == -1) {
|
|
293
|
+
// remove 1 from the expected redunancy since we got a route with negative 1 distance
|
|
294
|
+
// if we do not do this, we would get 2 routes if redundancy = 1, {-1, 0}, while it should just be
|
|
295
|
+
// {-1} in this case
|
|
296
|
+
redundancyModified -= 1;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
225
299
|
}
|
|
226
300
|
}
|
|
227
|
-
|
|
301
|
+
|
|
302
|
+
if (!foundClosest && from === this.me) {
|
|
228
303
|
return undefined; // we dont have the shortest path to our target (yet). Send to all
|
|
229
304
|
}
|
|
230
305
|
|
|
@@ -260,75 +335,4 @@ export class Routes {
|
|
|
260
335
|
}
|
|
261
336
|
return [];
|
|
262
337
|
}
|
|
263
|
-
|
|
264
|
-
public addPendingRouteConnection(
|
|
265
|
-
session: number,
|
|
266
|
-
route: {
|
|
267
|
-
from: string;
|
|
268
|
-
neighbour: string;
|
|
269
|
-
target: string;
|
|
270
|
-
distance: number;
|
|
271
|
-
}
|
|
272
|
-
) {
|
|
273
|
-
let map = this.pendingRoutes.get(session);
|
|
274
|
-
if (!map) {
|
|
275
|
-
map = new Map();
|
|
276
|
-
this.pendingRoutes.set(session, map);
|
|
277
|
-
}
|
|
278
|
-
let arr = map.get(route.target);
|
|
279
|
-
if (!arr) {
|
|
280
|
-
arr = [];
|
|
281
|
-
map.set(route.target, arr);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
arr.push(route);
|
|
285
|
-
|
|
286
|
-
const neighbour = this.findNeighbor(route.from, route.target);
|
|
287
|
-
if (!neighbour || neighbour.session === session) {
|
|
288
|
-
// Commit directly since we dont have any data at all (better have something than nothing)
|
|
289
|
-
this.commitPendingRouteConnection(session, route.target);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// always commit if we dont know the peer yet
|
|
294
|
-
// do pending commits per remote (?)
|
|
295
|
-
|
|
296
|
-
public commitPendingRouteConnection(session: number, target?: string) {
|
|
297
|
-
const map = this.pendingRoutes.get(session);
|
|
298
|
-
if (!map) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
if (target) {
|
|
302
|
-
const routes = map.get(target);
|
|
303
|
-
if (routes) {
|
|
304
|
-
for (const route of routes) {
|
|
305
|
-
this.add(
|
|
306
|
-
route.from,
|
|
307
|
-
route.neighbour,
|
|
308
|
-
target,
|
|
309
|
-
route.distance,
|
|
310
|
-
session
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
map.delete(target);
|
|
314
|
-
return;
|
|
315
|
-
} else {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
} else {
|
|
319
|
-
for (const [target, routes] of map) {
|
|
320
|
-
for (const route of routes) {
|
|
321
|
-
this.add(
|
|
322
|
-
route.from,
|
|
323
|
-
route.neighbour,
|
|
324
|
-
target,
|
|
325
|
-
route.distance,
|
|
326
|
-
session
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
this.pendingRoutes.delete(session);
|
|
333
|
-
}
|
|
334
338
|
}
|