@thru/replay 0.2.32 → 0.2.34
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/dist/index.cjs +299 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -41
- package/dist/index.d.ts +45 -41
- package/dist/index.mjs +299 -98
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -443,6 +443,7 @@ var DEFAULT_METRICS = {
|
|
|
443
443
|
emittedReconnect: 0,
|
|
444
444
|
discardedDuplicates: 0
|
|
445
445
|
};
|
|
446
|
+
var RECONNECT_STUCK_THRESHOLD_MS = 3e4;
|
|
446
447
|
function compareBigint(a, b) {
|
|
447
448
|
if (a === b) return 0;
|
|
448
449
|
return a < b ? -1 : 1;
|
|
@@ -644,6 +645,7 @@ var ReplayStream = class {
|
|
|
644
645
|
this.logger.info("replay entering STREAMING state");
|
|
645
646
|
const retryConfig = DEFAULT_RETRY_CONFIG;
|
|
646
647
|
let retryAttempt = 0;
|
|
648
|
+
let waitingFirstReconnectEvent = null;
|
|
647
649
|
while (true) {
|
|
648
650
|
if (shouldStop()) return;
|
|
649
651
|
try {
|
|
@@ -658,6 +660,16 @@ var ReplayStream = class {
|
|
|
658
660
|
throw new Error("stream ended");
|
|
659
661
|
}
|
|
660
662
|
const slot = extractSlot(next.value);
|
|
663
|
+
if (waitingFirstReconnectEvent) {
|
|
664
|
+
this.logger.info("Replay stream reconnect completed", {
|
|
665
|
+
event: "replay.stream.reconnect.completed",
|
|
666
|
+
attempt: waitingFirstReconnectEvent.attempt,
|
|
667
|
+
resume_slot: waitingFirstReconnectEvent.resumeSlot.toString(),
|
|
668
|
+
first_event_slot: slot.toString(),
|
|
669
|
+
duration_ms: Date.now() - waitingFirstReconnectEvent.startedAtMs
|
|
670
|
+
});
|
|
671
|
+
waitingFirstReconnectEvent = null;
|
|
672
|
+
}
|
|
661
673
|
const key = keyOf(next.value);
|
|
662
674
|
if (seenItem(slot, key)) {
|
|
663
675
|
this.metrics.discardedDuplicates += 1;
|
|
@@ -672,13 +684,45 @@ var ReplayStream = class {
|
|
|
672
684
|
if (shouldStop(err)) return;
|
|
673
685
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
674
686
|
const backoffMs = calculateBackoff(retryAttempt, retryConfig);
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
687
|
+
const attempt = retryAttempt + 1;
|
|
688
|
+
const reconnectStartedAtMs = Date.now();
|
|
689
|
+
this.logger.warn("Replay stream reconnect started", {
|
|
690
|
+
event: "replay.stream.reconnect.started",
|
|
691
|
+
reason: errMsg === "stream ended" ? "stream_ended" : "stream_error",
|
|
692
|
+
error: errMsg,
|
|
693
|
+
backoff_ms: backoffMs,
|
|
694
|
+
attempt,
|
|
695
|
+
current_slot: currentSlot.toString()
|
|
696
|
+
});
|
|
678
697
|
await abortableDelay(backoffMs, signal);
|
|
679
698
|
if (shouldStop()) return;
|
|
699
|
+
const cleanupStartedAtMs = Date.now();
|
|
700
|
+
this.logger.info("Replay stream reconnect cleanup started", {
|
|
701
|
+
event: "replay.stream.reconnect.cleanup_started",
|
|
702
|
+
phase: "live_pump_close",
|
|
703
|
+
attempt,
|
|
704
|
+
current_slot: currentSlot.toString()
|
|
705
|
+
});
|
|
680
706
|
currentDispose();
|
|
681
|
-
await safeClose(livePump);
|
|
707
|
+
const closeResult = await safeClose(livePump, signal);
|
|
708
|
+
const cleanupDurationMs = Date.now() - cleanupStartedAtMs;
|
|
709
|
+
if (closeResult === "timed-out") {
|
|
710
|
+
this.logger.warn("Replay stream reconnect cleanup stuck", {
|
|
711
|
+
event: "replay.stream.reconnect.stuck",
|
|
712
|
+
phase: "live_pump_close",
|
|
713
|
+
attempt,
|
|
714
|
+
duration_ms: cleanupDurationMs,
|
|
715
|
+
timeout_ms: RECONNECT_STUCK_THRESHOLD_MS
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
this.logger.info("Replay stream reconnect cleanup completed", {
|
|
719
|
+
event: "replay.stream.reconnect.cleanup_completed",
|
|
720
|
+
phase: "live_pump_close",
|
|
721
|
+
attempt,
|
|
722
|
+
duration_ms: cleanupDurationMs,
|
|
723
|
+
result: closeResult
|
|
724
|
+
});
|
|
725
|
+
if (shouldStop()) return;
|
|
682
726
|
retryAttempt++;
|
|
683
727
|
if (onReconnect) {
|
|
684
728
|
try {
|
|
@@ -689,11 +733,16 @@ var ReplayStream = class {
|
|
|
689
733
|
}
|
|
690
734
|
currentDispose = fresh.dispose ?? (() => {
|
|
691
735
|
});
|
|
692
|
-
this.logger.info("
|
|
736
|
+
this.logger.info("Replay stream fresh reconnect sources created", {
|
|
737
|
+
event: "replay.stream.reconnect.sources_created",
|
|
738
|
+
attempt
|
|
739
|
+
});
|
|
693
740
|
} catch (factoryErr) {
|
|
694
|
-
this.logger.error(
|
|
695
|
-
|
|
696
|
-
|
|
741
|
+
this.logger.error("Replay stream fresh reconnect sources failed", {
|
|
742
|
+
event: "replay.stream.reconnect.sources_failed",
|
|
743
|
+
attempt,
|
|
744
|
+
error: factoryErr
|
|
745
|
+
});
|
|
697
746
|
}
|
|
698
747
|
}
|
|
699
748
|
if (onReconnect && currentSlot > 0n) {
|
|
@@ -716,11 +765,21 @@ var ReplayStream = class {
|
|
|
716
765
|
}
|
|
717
766
|
const resumeSlot = currentSlot > 0n ? currentSlot : 0n;
|
|
718
767
|
livePump = createLivePump(resumeSlot, true, currentSlot);
|
|
768
|
+
waitingFirstReconnectEvent = {
|
|
769
|
+
startedAtMs: reconnectStartedAtMs,
|
|
770
|
+
attempt,
|
|
771
|
+
resumeSlot
|
|
772
|
+
};
|
|
773
|
+
this.logger.info("Replay stream waiting for first event", {
|
|
774
|
+
event: "replay.stream.waiting_first_event",
|
|
775
|
+
attempt,
|
|
776
|
+
resume_slot: resumeSlot.toString()
|
|
777
|
+
});
|
|
719
778
|
}
|
|
720
779
|
}
|
|
721
780
|
} finally {
|
|
722
781
|
currentDispose();
|
|
723
|
-
await safeClose(livePump);
|
|
782
|
+
await safeClose(livePump, signal);
|
|
724
783
|
}
|
|
725
784
|
}
|
|
726
785
|
/**
|
|
@@ -774,24 +833,45 @@ var ReplayStream = class {
|
|
|
774
833
|
}
|
|
775
834
|
}
|
|
776
835
|
};
|
|
777
|
-
async function safeClose(pump) {
|
|
836
|
+
async function safeClose(pump, signal) {
|
|
778
837
|
let timeoutId;
|
|
838
|
+
let onAbort;
|
|
779
839
|
try {
|
|
840
|
+
if (signal?.aborted) {
|
|
841
|
+
return "aborted";
|
|
842
|
+
}
|
|
780
843
|
const timeout = new Promise((resolve) => {
|
|
781
|
-
timeoutId = setTimeout(() => resolve("timeout"),
|
|
844
|
+
timeoutId = setTimeout(() => resolve("timeout"), RECONNECT_STUCK_THRESHOLD_MS);
|
|
782
845
|
});
|
|
846
|
+
const abort = signal ? new Promise((resolve) => {
|
|
847
|
+
onAbort = () => resolve("aborted");
|
|
848
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
849
|
+
}) : null;
|
|
850
|
+
const close = pump.close().then(
|
|
851
|
+
() => "closed",
|
|
852
|
+
() => "closed"
|
|
853
|
+
);
|
|
783
854
|
const result = await Promise.race([
|
|
784
|
-
|
|
785
|
-
timeout
|
|
855
|
+
close,
|
|
856
|
+
timeout,
|
|
857
|
+
...abort ? [abort] : []
|
|
786
858
|
]);
|
|
859
|
+
if (result === "aborted") {
|
|
860
|
+
return "aborted";
|
|
861
|
+
}
|
|
787
862
|
if (result === "timeout") {
|
|
863
|
+
return "timed-out";
|
|
788
864
|
}
|
|
789
865
|
} catch {
|
|
790
866
|
} finally {
|
|
791
867
|
if (timeoutId !== void 0) {
|
|
792
868
|
clearTimeout(timeoutId);
|
|
793
869
|
}
|
|
870
|
+
if (signal && onAbort) {
|
|
871
|
+
signal.removeEventListener("abort", onAbort);
|
|
872
|
+
}
|
|
794
873
|
}
|
|
874
|
+
return "closed";
|
|
795
875
|
}
|
|
796
876
|
function combineFilters(base, user) {
|
|
797
877
|
if (!base && !user) return void 0;
|
|
@@ -1269,6 +1349,7 @@ var PageAssembler = class {
|
|
|
1269
1349
|
};
|
|
1270
1350
|
|
|
1271
1351
|
// src/account-replay.ts
|
|
1352
|
+
var DEFAULT_RECONNECT_CLEANUP_TIMEOUT_MS = 3e4;
|
|
1272
1353
|
async function closeAsyncIterator(iterator) {
|
|
1273
1354
|
if (!iterator || typeof iterator.return !== "function") {
|
|
1274
1355
|
return;
|
|
@@ -1278,6 +1359,72 @@ async function closeAsyncIterator(iterator) {
|
|
|
1278
1359
|
} catch {
|
|
1279
1360
|
}
|
|
1280
1361
|
}
|
|
1362
|
+
async function waitForCleanup(promise, timeoutMs, label, logger, signal) {
|
|
1363
|
+
const startedAtMs = Date.now();
|
|
1364
|
+
logger.info("Replay stream reconnect cleanup started", {
|
|
1365
|
+
event: "replay.stream.reconnect.cleanup_started",
|
|
1366
|
+
phase: label,
|
|
1367
|
+
timeout_ms: timeoutMs
|
|
1368
|
+
});
|
|
1369
|
+
if (signal?.aborted) {
|
|
1370
|
+
logger.debug("Replay stream reconnect cleanup aborted", {
|
|
1371
|
+
event: "replay.stream.reconnect.cleanup_completed",
|
|
1372
|
+
phase: label,
|
|
1373
|
+
timeout_ms: timeoutMs,
|
|
1374
|
+
result: "aborted",
|
|
1375
|
+
duration_ms: 0
|
|
1376
|
+
});
|
|
1377
|
+
return "aborted";
|
|
1378
|
+
}
|
|
1379
|
+
let timer = null;
|
|
1380
|
+
let onAbort = null;
|
|
1381
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
1382
|
+
timer = setTimeout(() => resolve("timed-out"), timeoutMs);
|
|
1383
|
+
timer.unref?.();
|
|
1384
|
+
});
|
|
1385
|
+
const abortPromise = signal ? new Promise((resolve) => {
|
|
1386
|
+
onAbort = () => resolve("aborted");
|
|
1387
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1388
|
+
}) : null;
|
|
1389
|
+
const completionPromise = promise.then(
|
|
1390
|
+
() => "completed",
|
|
1391
|
+
() => "completed"
|
|
1392
|
+
);
|
|
1393
|
+
const result = await Promise.race(
|
|
1394
|
+
abortPromise ? [completionPromise, timeoutPromise, abortPromise] : [completionPromise, timeoutPromise]
|
|
1395
|
+
);
|
|
1396
|
+
if (timer) {
|
|
1397
|
+
clearTimeout(timer);
|
|
1398
|
+
}
|
|
1399
|
+
if (signal && onAbort) {
|
|
1400
|
+
signal.removeEventListener("abort", onAbort);
|
|
1401
|
+
}
|
|
1402
|
+
if (result === "timed-out") {
|
|
1403
|
+
logger.warn("Replay stream reconnect cleanup stuck", {
|
|
1404
|
+
event: "replay.stream.reconnect.stuck",
|
|
1405
|
+
phase: label,
|
|
1406
|
+
timeout_ms: timeoutMs,
|
|
1407
|
+
duration_ms: Date.now() - startedAtMs
|
|
1408
|
+
});
|
|
1409
|
+
} else if (result === "aborted") {
|
|
1410
|
+
logger.debug("Replay stream reconnect cleanup aborted", {
|
|
1411
|
+
event: "replay.stream.reconnect.cleanup_completed",
|
|
1412
|
+
phase: label,
|
|
1413
|
+
timeout_ms: timeoutMs,
|
|
1414
|
+
result,
|
|
1415
|
+
duration_ms: Date.now() - startedAtMs
|
|
1416
|
+
});
|
|
1417
|
+
} else {
|
|
1418
|
+
logger.info("Replay stream reconnect cleanup completed", {
|
|
1419
|
+
event: "replay.stream.reconnect.cleanup_completed",
|
|
1420
|
+
phase: label,
|
|
1421
|
+
timeout_ms: timeoutMs,
|
|
1422
|
+
result,
|
|
1423
|
+
duration_ms: Date.now() - startedAtMs
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
return result;
|
|
1427
|
+
}
|
|
1281
1428
|
function bytesToHex2(bytes) {
|
|
1282
1429
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1283
1430
|
}
|
|
@@ -1350,6 +1497,8 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1350
1497
|
maxRetries = 3,
|
|
1351
1498
|
pageAssemblerOptions,
|
|
1352
1499
|
cleanupInterval = 1e4,
|
|
1500
|
+
reconnectCleanupTimeoutMs = DEFAULT_RECONNECT_CLEANUP_TIMEOUT_MS,
|
|
1501
|
+
retryConfig = DEFAULT_RETRY_CONFIG,
|
|
1353
1502
|
onBackfillComplete,
|
|
1354
1503
|
clientFactory,
|
|
1355
1504
|
logger = NOOP_LOGGER,
|
|
@@ -1369,23 +1518,98 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1369
1518
|
let lastActivityTime = Date.now();
|
|
1370
1519
|
let activeStreamIterator = null;
|
|
1371
1520
|
let activeStreamProcessor = null;
|
|
1372
|
-
|
|
1521
|
+
let streamGeneration = 0;
|
|
1522
|
+
let retryAttempt = 0;
|
|
1523
|
+
const retireActiveStream = () => {
|
|
1524
|
+
const iterator = activeStreamIterator;
|
|
1525
|
+
const processor = activeStreamProcessor;
|
|
1526
|
+
activeStreamIterator = null;
|
|
1527
|
+
activeStreamProcessor = null;
|
|
1528
|
+
streamGeneration++;
|
|
1529
|
+
return { iterator, processor };
|
|
1530
|
+
};
|
|
1531
|
+
const cleanupRetiredStream = async (retired, iteratorLabel, processorLabel) => {
|
|
1532
|
+
if (retired.iterator) {
|
|
1533
|
+
await waitForCleanup(
|
|
1534
|
+
closeAsyncIterator(retired.iterator),
|
|
1535
|
+
reconnectCleanupTimeoutMs,
|
|
1536
|
+
iteratorLabel,
|
|
1537
|
+
logger,
|
|
1538
|
+
signal
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1373
1541
|
if (shouldStop()) return;
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1542
|
+
if (retired.processor) {
|
|
1543
|
+
await waitForCleanup(
|
|
1544
|
+
Promise.allSettled([retired.processor]),
|
|
1545
|
+
reconnectCleanupTimeoutMs,
|
|
1546
|
+
processorLabel,
|
|
1547
|
+
logger,
|
|
1548
|
+
signal
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
const createFreshClient = () => {
|
|
1553
|
+
if (!clientFactory) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
logger.info("Replay stream fresh client creation started", {
|
|
1557
|
+
event: "replay.stream.reconnect.client_started"
|
|
1558
|
+
});
|
|
1559
|
+
try {
|
|
1560
|
+
const previousClient = client;
|
|
1561
|
+
const newClient = clientFactory();
|
|
1562
|
+
closeIfCloseable(previousClient);
|
|
1563
|
+
client = newClient;
|
|
1564
|
+
logger.info("Replay stream fresh client creation completed", {
|
|
1565
|
+
event: "replay.stream.reconnect.client_completed"
|
|
1566
|
+
});
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
logger.error("Replay stream fresh client creation failed", {
|
|
1569
|
+
event: "replay.stream.reconnect.client_failed",
|
|
1570
|
+
error: err
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
const createStreamProcessor = (reason = "initial") => {
|
|
1575
|
+
const minSlot = highestSlotSeen > 0n ? highestSlotSeen : minUpdatedSlot;
|
|
1576
|
+
const generation = ++streamGeneration;
|
|
1577
|
+
const streamStartedAtMs = Date.now();
|
|
1578
|
+
let firstMessageSeen = false;
|
|
1579
|
+
if (reason === "reconnect") {
|
|
1580
|
+
logger.info("Replay stream waiting for first event", {
|
|
1581
|
+
event: "replay.stream.waiting_first_event",
|
|
1582
|
+
generation,
|
|
1583
|
+
min_slot: minSlot?.toString()
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
const newStreamFilter = buildOwnerFilterWithMinSlot(owner, dataSizes, minSlot);
|
|
1587
|
+
const newStream = client.streamAccountUpdates({ view, filter: newStreamFilter });
|
|
1588
|
+
const newStreamIterator = newStream[Symbol.asyncIterator]();
|
|
1589
|
+
streamDone = false;
|
|
1590
|
+
streamError = null;
|
|
1591
|
+
lastActivityTime = Date.now();
|
|
1592
|
+
activeStreamIterator = newStreamIterator;
|
|
1593
|
+
const newProcessor = (async () => {
|
|
1383
1594
|
try {
|
|
1384
1595
|
while (true) {
|
|
1385
|
-
const next = await
|
|
1596
|
+
const next = await newStreamIterator.next();
|
|
1597
|
+
if (generation !== streamGeneration) {
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1386
1600
|
if (next.done) break;
|
|
1387
1601
|
const response = next.value;
|
|
1602
|
+
retryAttempt = 0;
|
|
1388
1603
|
lastActivityTime = Date.now();
|
|
1604
|
+
if (reason === "reconnect" && !firstMessageSeen) {
|
|
1605
|
+
firstMessageSeen = true;
|
|
1606
|
+
logger.info("Replay stream reconnect completed", {
|
|
1607
|
+
event: "replay.stream.reconnect.completed",
|
|
1608
|
+
generation,
|
|
1609
|
+
first_message: true,
|
|
1610
|
+
duration_ms: Date.now() - streamStartedAtMs
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1389
1613
|
const event = processResponseMulti(response, assembler);
|
|
1390
1614
|
if (event) {
|
|
1391
1615
|
if (event.type === "account") {
|
|
@@ -1398,11 +1622,24 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1398
1622
|
}
|
|
1399
1623
|
}
|
|
1400
1624
|
} catch (err) {
|
|
1401
|
-
|
|
1625
|
+
if (generation === streamGeneration) {
|
|
1626
|
+
streamError = err;
|
|
1627
|
+
}
|
|
1402
1628
|
} finally {
|
|
1403
|
-
|
|
1629
|
+
if (generation === streamGeneration) {
|
|
1630
|
+
streamDone = true;
|
|
1631
|
+
}
|
|
1404
1632
|
}
|
|
1405
1633
|
})();
|
|
1634
|
+
activeStreamProcessor = newProcessor;
|
|
1635
|
+
};
|
|
1636
|
+
try {
|
|
1637
|
+
if (shouldStop()) return;
|
|
1638
|
+
cleanupTimer = setInterval(() => {
|
|
1639
|
+
assembler.cleanup();
|
|
1640
|
+
}, cleanupInterval);
|
|
1641
|
+
cleanupTimer.unref?.();
|
|
1642
|
+
createStreamProcessor();
|
|
1406
1643
|
const yieldStreamBuffer = function* () {
|
|
1407
1644
|
while (streamBuffer.length > 0) {
|
|
1408
1645
|
const event = streamBuffer.shift();
|
|
@@ -1480,51 +1717,8 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1480
1717
|
if (onBackfillComplete) {
|
|
1481
1718
|
onBackfillComplete(highestSlotSeen);
|
|
1482
1719
|
}
|
|
1483
|
-
|
|
1484
|
-
let retryAttempt = 0;
|
|
1720
|
+
retryAttempt = 0;
|
|
1485
1721
|
lastActivityTime = Date.now();
|
|
1486
|
-
const createStreamProcessor = () => {
|
|
1487
|
-
if (clientFactory) {
|
|
1488
|
-
try {
|
|
1489
|
-
const newClient = clientFactory();
|
|
1490
|
-
closeIfCloseable(client);
|
|
1491
|
-
client = newClient;
|
|
1492
|
-
logger.info("[account-stream] created fresh client for reconnection");
|
|
1493
|
-
} catch (err) {
|
|
1494
|
-
logger.error("[account-stream] failed to create fresh client", { error: err });
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
const newStreamFilter = buildOwnerFilterWithMinSlot(owner, dataSizes, highestSlotSeen > 0n ? highestSlotSeen : minUpdatedSlot);
|
|
1498
|
-
const newStream = client.streamAccountUpdates({ view, filter: newStreamFilter });
|
|
1499
|
-
const newStreamIterator = newStream[Symbol.asyncIterator]();
|
|
1500
|
-
activeStreamIterator = newStreamIterator;
|
|
1501
|
-
const newProcessor = (async () => {
|
|
1502
|
-
try {
|
|
1503
|
-
while (true) {
|
|
1504
|
-
const next = await newStreamIterator.next();
|
|
1505
|
-
if (next.done) break;
|
|
1506
|
-
const response = next.value;
|
|
1507
|
-
retryAttempt = 0;
|
|
1508
|
-
lastActivityTime = Date.now();
|
|
1509
|
-
const event = processResponseMulti(response, assembler);
|
|
1510
|
-
if (event) {
|
|
1511
|
-
if (event.type === "account") {
|
|
1512
|
-
seenFromStream.add(event.account.addressHex);
|
|
1513
|
-
if (event.account.slot > highestSlotSeen) {
|
|
1514
|
-
highestSlotSeen = event.account.slot;
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
streamBuffer.push(event);
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
} catch (err) {
|
|
1521
|
-
streamError = err;
|
|
1522
|
-
} finally {
|
|
1523
|
-
streamDone = true;
|
|
1524
|
-
}
|
|
1525
|
-
})();
|
|
1526
|
-
return { iterator: newStreamIterator, processor: newProcessor };
|
|
1527
|
-
};
|
|
1528
1722
|
while (true) {
|
|
1529
1723
|
if (shouldStop()) return;
|
|
1530
1724
|
const hadEvents = streamBuffer.length > 0;
|
|
@@ -1532,10 +1726,13 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1532
1726
|
if (hadEvents) {
|
|
1533
1727
|
lastActivityTime = Date.now();
|
|
1534
1728
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1729
|
+
const idleMs = Date.now() - lastActivityTime;
|
|
1730
|
+
if (!streamDone && idleMs > retryConfig.connectionTimeoutMs) {
|
|
1731
|
+
logger.warn("Replay stream idle timeout detected", {
|
|
1732
|
+
event: "replay.stream.idle_timeout",
|
|
1733
|
+
idleMs,
|
|
1734
|
+
connectionTimeoutMs: retryConfig.connectionTimeoutMs
|
|
1735
|
+
});
|
|
1539
1736
|
streamDone = true;
|
|
1540
1737
|
streamError = new Error(`Operation timed out after ${retryConfig.connectionTimeoutMs}ms`);
|
|
1541
1738
|
}
|
|
@@ -1543,36 +1740,44 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1543
1740
|
if (streamError) {
|
|
1544
1741
|
if (shouldStop(streamError)) return;
|
|
1545
1742
|
const backoffMs = calculateBackoff(retryAttempt, retryConfig);
|
|
1546
|
-
logger.warn(
|
|
1547
|
-
|
|
1548
|
-
|
|
1743
|
+
logger.warn("Replay stream reconnect started", {
|
|
1744
|
+
event: "replay.stream.reconnect.started",
|
|
1745
|
+
reason: "stream_error",
|
|
1746
|
+
error: streamError.message,
|
|
1747
|
+
backoffMs,
|
|
1748
|
+
attempt: retryAttempt + 1,
|
|
1749
|
+
highestSlotSeen: highestSlotSeen.toString()
|
|
1750
|
+
});
|
|
1549
1751
|
await abortableDelay(backoffMs, signal);
|
|
1550
1752
|
if (shouldStop()) return;
|
|
1551
1753
|
retryAttempt++;
|
|
1754
|
+
const retired = retireActiveStream();
|
|
1552
1755
|
streamDone = false;
|
|
1553
1756
|
streamError = null;
|
|
1554
1757
|
streamBuffer.length = 0;
|
|
1555
1758
|
lastActivityTime = Date.now();
|
|
1556
|
-
await
|
|
1557
|
-
if (
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
const { iterator: newIterator, processor: newProcessor } = createStreamProcessor();
|
|
1561
|
-
activeStreamIterator = newIterator;
|
|
1562
|
-
activeStreamProcessor = newProcessor;
|
|
1759
|
+
await cleanupRetiredStream(retired, "old iterator close", "old processor drain");
|
|
1760
|
+
if (shouldStop()) return;
|
|
1761
|
+
createFreshClient();
|
|
1762
|
+
createStreamProcessor("reconnect");
|
|
1563
1763
|
continue;
|
|
1564
1764
|
} else {
|
|
1565
1765
|
if (shouldStop()) return;
|
|
1566
|
-
logger.warn("
|
|
1766
|
+
logger.warn("Replay stream reconnect started", {
|
|
1767
|
+
event: "replay.stream.reconnect.started",
|
|
1768
|
+
reason: "stream_ended",
|
|
1769
|
+
attempt: retryAttempt + 1,
|
|
1770
|
+
highestSlotSeen: highestSlotSeen.toString()
|
|
1771
|
+
});
|
|
1772
|
+
const retired = retireActiveStream();
|
|
1567
1773
|
streamDone = false;
|
|
1774
|
+
streamError = null;
|
|
1775
|
+
streamBuffer.length = 0;
|
|
1568
1776
|
lastActivityTime = Date.now();
|
|
1569
|
-
await
|
|
1570
|
-
if (
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
const { iterator: newIterator, processor: newProcessor } = createStreamProcessor();
|
|
1574
|
-
activeStreamIterator = newIterator;
|
|
1575
|
-
activeStreamProcessor = newProcessor;
|
|
1777
|
+
await cleanupRetiredStream(retired, "old iterator close", "old processor drain");
|
|
1778
|
+
if (shouldStop()) return;
|
|
1779
|
+
createFreshClient();
|
|
1780
|
+
createStreamProcessor("reconnect");
|
|
1576
1781
|
continue;
|
|
1577
1782
|
}
|
|
1578
1783
|
}
|
|
@@ -1582,15 +1787,11 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1582
1787
|
if (cleanupTimer) {
|
|
1583
1788
|
clearInterval(cleanupTimer);
|
|
1584
1789
|
}
|
|
1585
|
-
const
|
|
1790
|
+
const retired = retireActiveStream();
|
|
1586
1791
|
if (ownsClient) {
|
|
1587
1792
|
closeIfCloseable(client);
|
|
1588
1793
|
}
|
|
1589
|
-
|
|
1590
|
-
await Promise.allSettled([closeIteratorPromise, activeStreamProcessor]);
|
|
1591
|
-
} else {
|
|
1592
|
-
await closeIteratorPromise;
|
|
1593
|
-
}
|
|
1794
|
+
await cleanupRetiredStream(retired, "final iterator close", "final processor drain");
|
|
1594
1795
|
assembler.clear();
|
|
1595
1796
|
}
|
|
1596
1797
|
}
|