@peerbit/shared-log 12.2.0-874976b → 12.2.0-90d77b6

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.
Files changed (32) hide show
  1. package/dist/benchmark/pid-convergence.d.ts +2 -0
  2. package/dist/benchmark/pid-convergence.d.ts.map +1 -0
  3. package/dist/benchmark/pid-convergence.js +138 -0
  4. package/dist/benchmark/pid-convergence.js.map +1 -0
  5. package/dist/benchmark/rateless-iblt-sender-startsync.d.ts +2 -0
  6. package/dist/benchmark/rateless-iblt-sender-startsync.d.ts.map +1 -0
  7. package/dist/benchmark/rateless-iblt-sender-startsync.js +104 -0
  8. package/dist/benchmark/rateless-iblt-sender-startsync.js.map +1 -0
  9. package/dist/benchmark/rateless-iblt-startsync-cache.d.ts +2 -0
  10. package/dist/benchmark/rateless-iblt-startsync-cache.d.ts.map +1 -0
  11. package/dist/benchmark/rateless-iblt-startsync-cache.js +112 -0
  12. package/dist/benchmark/rateless-iblt-startsync-cache.js.map +1 -0
  13. package/dist/benchmark/sync-catchup.d.ts +3 -0
  14. package/dist/benchmark/sync-catchup.d.ts.map +1 -0
  15. package/dist/benchmark/sync-catchup.js +109 -0
  16. package/dist/benchmark/sync-catchup.js.map +1 -0
  17. package/dist/src/index.d.ts +14 -2
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js +256 -82
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/ranges.d.ts +1 -0
  22. package/dist/src/ranges.d.ts.map +1 -1
  23. package/dist/src/ranges.js +48 -18
  24. package/dist/src/ranges.js.map +1 -1
  25. package/dist/src/sync/rateless-iblt.d.ts +8 -0
  26. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  27. package/dist/src/sync/rateless-iblt.js +109 -20
  28. package/dist/src/sync/rateless-iblt.js.map +1 -1
  29. package/package.json +18 -18
  30. package/src/index.ts +324 -125
  31. package/src/ranges.ts +97 -65
  32. package/src/sync/rateless-iblt.ts +138 -28
package/src/ranges.ts CHANGED
@@ -2064,6 +2064,7 @@ export const getSamples = async <R extends "u32" | "u64">(
2064
2064
  options?: {
2065
2065
  onlyIntersecting?: boolean;
2066
2066
  uniqueReplicators?: Set<string>;
2067
+ peerFilter?: Set<string>;
2067
2068
  },
2068
2069
  ): Promise<Map<string, { intersecting: boolean }>> => {
2069
2070
  const leaders: Map<string, { intersecting: boolean }> = new Map();
@@ -2075,6 +2076,7 @@ export const getSamples = async <R extends "u32" | "u64">(
2075
2076
  let matured = 0;
2076
2077
 
2077
2078
  let uniqueVisited = new Set<string>();
2079
+ const peerFilter = options?.peerFilter;
2078
2080
  for (let i = 0; i < cursor.length; i++) {
2079
2081
  let point = cursor[i];
2080
2082
 
@@ -2084,6 +2086,9 @@ export const getSamples = async <R extends "u32" | "u64">(
2084
2086
  );
2085
2087
 
2086
2088
  for (const rect of allContaining) {
2089
+ if (peerFilter && !peerFilter.has(rect.value.hash)) {
2090
+ continue;
2091
+ }
2087
2092
  uniqueVisited.add(rect.value.hash);
2088
2093
  let prev = leaders.get(rect.value.hash);
2089
2094
  if (!prev) {
@@ -2096,7 +2101,7 @@ export const getSamples = async <R extends "u32" | "u64">(
2096
2101
  }
2097
2102
  }
2098
2103
 
2099
- if (options?.uniqueReplicators) {
2104
+ if (options?.uniqueReplicators && options.uniqueReplicators.size > 0) {
2100
2105
  if (
2101
2106
  options.uniqueReplicators.size === leaders.size ||
2102
2107
  options.uniqueReplicators.size === uniqueVisited.size
@@ -2114,6 +2119,9 @@ export const getSamples = async <R extends "u32" | "u64">(
2114
2119
  roleAge,
2115
2120
  peers,
2116
2121
  (rect, m) => {
2122
+ if (peerFilter && !peerFilter.has(rect.hash)) {
2123
+ return;
2124
+ }
2117
2125
  uniqueVisited.add(rect.hash);
2118
2126
  const prev = leaders.get(rect.hash);
2119
2127
  if (m) {
@@ -2234,7 +2242,7 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
2234
2242
  nextLocation: NumberFromType<R>,
2235
2243
  roleAge: number,
2236
2244
  ) => {
2237
- let next = await fetchOne(
2245
+ const next = await fetchOne(
2238
2246
  iterateRangesContainingPoint<undefined, R>(peers, nextLocation, {
2239
2247
  sort: [new Sort({ key: "end2", direction: SortDirection.DESC })],
2240
2248
  time: {
@@ -2243,31 +2251,31 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
2243
2251
  now,
2244
2252
  },
2245
2253
  }),
2246
- ); // get entersecting sort by largest end2
2254
+ ); // get intersecting sort by largest end2
2247
2255
  return next;
2248
2256
  };
2249
2257
 
2250
- const resolveNextAbove = async (
2251
- nextLocation: NumberFromType<R>,
2252
- roleAge: number,
2253
- ) => {
2254
- // if not get closest from above
2255
- let next = await fetchOne<undefined, R>(
2256
- getClosest("above", peers, nextLocation, true, properties.numbers, {
2257
- time: {
2258
- matured: true,
2259
- roleAgeLimit: roleAge,
2260
- now,
2261
- },
2262
- }),
2263
- );
2264
- return next;
2265
- };
2258
+ const resolveNextAbove = async (
2259
+ nextLocation: NumberFromType<R>,
2260
+ roleAge: number,
2261
+ ) => {
2262
+ // if not get closest from above
2263
+ const next = await fetchOne<undefined, R>(
2264
+ getClosest("above", peers, nextLocation, true, properties.numbers, {
2265
+ time: {
2266
+ matured: true,
2267
+ roleAgeLimit: roleAge,
2268
+ now,
2269
+ },
2270
+ }),
2271
+ );
2272
+ return next;
2273
+ };
2266
2274
 
2267
2275
  const resolveNext = async (
2268
2276
  nextLocation: NumberFromType<R>,
2269
2277
  roleAge: number,
2270
- ): Promise<[ReplicationRangeIndexable<R>, boolean]> => {
2278
+ ): Promise<[ReplicationRangeIndexable<R> | undefined, boolean]> => {
2271
2279
  const containing = await resolveNextContaining(nextLocation, roleAge);
2272
2280
  if (containing) {
2273
2281
  return [containing, true];
@@ -2288,12 +2296,25 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
2288
2296
  from: NumberFromType<R>,
2289
2297
  ) => {
2290
2298
  const toEnd2 = properties.numbers.increment(to.end2); // TODO investigate why this is needed
2291
- if (toEnd2 < from || to.wrapped) {
2299
+ if (toEnd2 < from) {
2292
2300
  wrappedOnce = true;
2293
2301
  // @ts-ignore
2294
2302
  coveredLength += properties.numbers.maxValue - from;
2295
2303
  // @ts-ignore
2296
2304
  coveredLength += toEnd2;
2305
+ } else if (to.wrapped) {
2306
+ // When the range is wrapped and `from` is in the second segment (near zero),
2307
+ // the distance to `end2` does not wrap. Otherwise we must wrap to reach `end2`.
2308
+ if (from < to.end2) {
2309
+ // @ts-ignore
2310
+ coveredLength += toEnd2 - from;
2311
+ } else {
2312
+ wrappedOnce = true;
2313
+ // @ts-ignore
2314
+ coveredLength += properties.numbers.maxValue - from;
2315
+ // @ts-ignore
2316
+ coveredLength += toEnd2;
2317
+ }
2297
2318
  } else {
2298
2319
  // @ts-ignore
2299
2320
  coveredLength += to.end1 - from;
@@ -2314,6 +2335,7 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
2314
2335
  (coveredLength <= properties.numbers.maxValue || !wrappedOnce) // eslint-disable-line no-unmodified-loop-condition
2315
2336
  ) {
2316
2337
  let distanceBefore = coveredLength;
2338
+ const nextLocationBefore = nextLocation;
2317
2339
 
2318
2340
  let nextCandidate = await resolveNext(nextLocation, roleAge);
2319
2341
  let matured = true;
@@ -2343,55 +2365,53 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
2343
2365
  let last = current;
2344
2366
  current = nextCandidate[0];
2345
2367
 
2346
- let isLast =
2347
- distanceBefore < widthToCoverScaled &&
2348
- coveredLength >= widthToCoverScaled;
2368
+ const isLast =
2369
+ distanceBefore < widthToCoverScaled && coveredLength >= widthToCoverScaled;
2370
+
2371
+ const lastDistanceToEndLocation = properties.numbers.min(
2372
+ getDistance(
2373
+ last.start1,
2374
+ endLocation,
2375
+ "closest",
2376
+ properties.numbers.maxValue,
2377
+ ),
2378
+ getDistance(
2379
+ last.end2,
2380
+ endLocation,
2381
+ "closest",
2382
+ properties.numbers.maxValue,
2383
+ ),
2384
+ );
2385
+
2386
+ const currentDistanceToEndLocation = properties.numbers.min(
2387
+ getDistance(
2388
+ current.start1,
2389
+ endLocation,
2390
+ "closest",
2391
+ properties.numbers.maxValue,
2392
+ ),
2393
+ getDistance(
2394
+ current.end2,
2395
+ endLocation,
2396
+ "closest",
2397
+ properties.numbers.maxValue,
2398
+ ),
2399
+ );
2349
2400
 
2350
2401
  if (
2351
2402
  !isLast ||
2352
2403
  nextCandidate[1] ||
2353
- properties.numbers.min(
2354
- getDistance(
2355
- last.start1,
2356
- endLocation,
2357
- "closest",
2358
- properties.numbers.maxValue,
2359
- ),
2360
- getDistance(
2361
- last.end2,
2362
- endLocation,
2363
- "closest",
2364
- properties.numbers.maxValue,
2365
- ),
2366
- ) >
2367
- properties.numbers.min(
2368
- getDistance(
2369
- current.start1,
2370
- endLocation,
2371
- "closest",
2372
- properties.numbers.maxValue,
2373
- ),
2374
- getDistance(
2375
- current.end2,
2376
- endLocation,
2377
- "closest",
2378
- properties.numbers.maxValue,
2379
- ),
2380
- )
2404
+ lastDistanceToEndLocation >= currentDistanceToEndLocation
2381
2405
  ) {
2382
2406
  ret.add(current.hash);
2383
2407
  }
2384
2408
 
2385
- if (isLast && !nextCandidate[1] /* || equals(endRect.id, current.id) */) {
2386
- break;
2387
- }
2388
-
2389
2409
  if (matured) {
2390
2410
  maturedCoveredLength = coveredLength;
2391
2411
  }
2392
2412
 
2393
2413
  let startForNext = extraDistanceForNext
2394
- ? properties.numbers.increment(current.end2)
2414
+ ? properties.numbers.increment(nextLocation)
2395
2415
  : current.end2;
2396
2416
  nextLocation = endIsWrapped
2397
2417
  ? wrappedOnce
@@ -2399,6 +2419,15 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
2399
2419
  : startForNext
2400
2420
  : properties.numbers.min(startForNext, endLocation);
2401
2421
 
2422
+ // Safety: ensure we always make progress to avoid infinite loops (can happen when
2423
+ // the chosen range is the same and `nextLocation` doesn't advance).
2424
+ if (
2425
+ nextLocation === nextLocationBefore &&
2426
+ coveredLength === distanceBefore
2427
+ ) {
2428
+ break;
2429
+ }
2430
+
2402
2431
  if (
2403
2432
  (typeof nextLocation === "bigint" &&
2404
2433
  nextLocation === (endLocation as bigint)) ||
@@ -2603,19 +2632,19 @@ export const mergeReplicationChanges = <R extends NumericType>(
2603
2632
  let results: ReplicationChange<ReplicationRangeIndexable<R>>[] = [];
2604
2633
  let consumed: Set<number> = new Set();
2605
2634
  for (let i = 0; i < v.length; i++) {
2606
- // if segment is removed and we have previously processed it
2607
- // then go over each overlapping added segment add remove the removal,
2608
- // equivalent is that this would represent (1 - 1 + 1) = 1
2635
+ // If segment is removed and we have previously processed it then go over each
2636
+ // overlapping added segment and remove the overlap. Equivalent to: (1 - 1 + 1) = 1.
2609
2637
  if (v[i].type === "removed" || v[i].type === "replaced") {
2610
2638
  if (rebalanceHistory.has(v[i].range.rangeHash)) {
2611
- let vStart = v.length;
2639
+ let adjusted = false;
2640
+ const vStart = v.length;
2612
2641
  for (let j = i + 1; j < vStart; j++) {
2613
2642
  const newer = v[j];
2614
2643
  if (newer.type === "added" && !newer.matured) {
2615
- const {
2616
- rangesFromA: updatedRemoved,
2617
- rangesFromB: updatedNewer,
2618
- } = symmetricDifferenceRanges(v[i].range, newer.range);
2644
+ adjusted = true;
2645
+ const { rangesFromA: updatedRemoved, rangesFromB: updatedNewer } =
2646
+ symmetricDifferenceRanges(v[i].range, newer.range);
2647
+
2619
2648
  for (const diff of updatedRemoved) {
2620
2649
  results.push({
2621
2650
  range: diff,
@@ -2634,6 +2663,9 @@ export const mergeReplicationChanges = <R extends NumericType>(
2634
2663
  }
2635
2664
  }
2636
2665
  rebalanceHistory.del(v[i].range.rangeHash);
2666
+ if (!adjusted) {
2667
+ results.push(v[i]);
2668
+ }
2637
2669
  } else {
2638
2670
  results.push(v[i]);
2639
2671
  }
@@ -187,6 +187,13 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
187
187
  simple: SimpleSyncronizer<D>;
188
188
 
189
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
+
190
197
  ingoingSyncProcesses: Map<
191
198
  string,
192
199
  {
@@ -222,6 +229,91 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
222
229
  this.startedOrCompletedSynchronizations = new Cache({ max: 1e4 });
223
230
  }
224
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
+
225
317
  async onMaybeMissingEntries(properties: {
226
318
  entries: Map<string, EntryReplicated<D>>;
227
319
  targets: string[];
@@ -292,9 +384,13 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
292
384
  }
293
385
  }
294
386
 
295
- let allCoordinatesToSyncWithIblt = nonBoundaryEntries
296
- .filter((entry) => !entriesToSyncNaively.has(entry.hash))
297
- .map((entry) => coerceBigInt(entry.hashNumber));
387
+ let allCoordinatesToSyncWithIblt: bigint[] = [];
388
+ for (const entry of nonBoundaryEntries) {
389
+ if (entriesToSyncNaively.has(entry.hash)) {
390
+ continue;
391
+ }
392
+ allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
393
+ }
298
394
 
299
395
  if (entriesToSyncNaively.size > 0) {
300
396
  // If there are special-case entries, sync them simply in parallel
@@ -309,9 +405,10 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
309
405
  entriesToSyncNaively.size > maxSyncWithSimpleMethod
310
406
  ) {
311
407
  // Fallback: if nothing left for IBLT (or simple set is too large), include all in IBLT
312
- allCoordinatesToSyncWithIblt = Array.from(
313
- properties.entries.values(),
314
- ).map((x) => coerceBigInt(x.hashNumber));
408
+ allCoordinatesToSyncWithIblt = [];
409
+ for (const entry of properties.entries.values()) {
410
+ allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
411
+ }
315
412
  }
316
413
 
317
414
  if (allCoordinatesToSyncWithIblt.length === 0) {
@@ -320,22 +417,32 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
320
417
 
321
418
  await ribltReady;
322
419
 
323
- const sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
324
- if (a > b) {
325
- return 1;
326
- } else if (a < b) {
327
- return -1;
328
- } else {
329
- return 0;
420
+ let sortedEntries: bigint[] | BigUint64Array;
421
+ if (typeof BigUint64Array !== "undefined") {
422
+ const typed = new BigUint64Array(allCoordinatesToSyncWithIblt.length);
423
+ for (let i = 0; i < allCoordinatesToSyncWithIblt.length; i++) {
424
+ typed[i] = allCoordinatesToSyncWithIblt[i];
330
425
  }
331
- });
426
+ typed.sort();
427
+ sortedEntries = typed;
428
+ } else {
429
+ sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
430
+ if (a > b) {
431
+ return 1;
432
+ } else if (a < b) {
433
+ return -1;
434
+ } else {
435
+ return 0;
436
+ }
437
+ });
438
+ }
332
439
 
333
440
  // assume sorted, and find the largest gap
334
441
  let largestGap = 0n;
335
442
  let largestGapIndex = 0;
336
- for (let i = 0; i < sortedEntries.length - 1; i++) {
443
+ for (let i = 0; i < sortedEntries.length; i++) {
337
444
  const current = sortedEntries[i];
338
- const next = sortedEntries[i + 1];
445
+ const next = sortedEntries[(i + 1) % sortedEntries.length];
339
446
  const gap =
340
447
  next >= current
341
448
  ? next - current
@@ -365,8 +472,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
365
472
 
366
473
  const startSync = new StartSync({ from: start, to: end, symbols: [] });
367
474
  const encoder = new EncoderWrapper();
368
- for (const entry of sortedEntries) {
369
- encoder.add_symbol(coerceBigInt(entry));
475
+ if (typeof BigUint64Array !== "undefined" && sortedEntries instanceof BigUint64Array) {
476
+ encoder.add_symbols(sortedEntries);
477
+ } else {
478
+ for (const entry of sortedEntries) {
479
+ encoder.add_symbol(coerceBigInt(entry));
480
+ }
370
481
  }
371
482
 
372
483
  let initialSymbols = Math.round(
@@ -442,16 +553,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
442
553
  this.startedOrCompletedSynchronizations.add(syncId);
443
554
 
444
555
  const wrapped = message.end < message.start;
445
- const decoder = await buildEncoderOrDecoderFromRange(
446
- {
447
- start1: message.start,
448
- end1: wrapped ? this.properties.numbers.maxValue : message.end,
449
- start2: 0n,
450
- end2: wrapped ? message.end : 0n,
451
- },
452
- this.properties.entryIndex,
453
- "decoder",
454
- );
556
+ const decoder = await this.getLocalDecoderForRange({
557
+ start1: message.start,
558
+ end1: wrapped ? this.properties.numbers.maxValue : message.end,
559
+ start2: 0n,
560
+ end2: wrapped ? message.end : 0n,
561
+ });
455
562
 
456
563
  if (!decoder) {
457
564
  await this.simple.rpc.send(
@@ -656,10 +763,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
656
763
  }
657
764
 
658
765
  onEntryAdded(entry: Entry<any>): void {
766
+ this.invalidateLocalRangeEncoderCache();
659
767
  return this.simple.onEntryAdded(entry);
660
768
  }
661
769
 
662
770
  onEntryRemoved(hash: string) {
771
+ this.invalidateLocalRangeEncoderCache();
663
772
  return this.simple.onEntryRemoved(hash);
664
773
  }
665
774
 
@@ -678,6 +787,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
678
787
  for (const [, obj] of this.outgoingSyncProcesses) {
679
788
  obj.free();
680
789
  }
790
+ this.clearLocalRangeEncoderCache();
681
791
  return this.simple.close();
682
792
  }
683
793