@thru/replay 0.2.4 → 0.2.6
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 +45 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +45 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -418,12 +418,26 @@ var ReplayStream = class {
|
|
|
418
418
|
this.logger.info(
|
|
419
419
|
`replay entering BACKFILLING state (startSlot=${startSlot}, safetyMargin=${safetyMargin})`
|
|
420
420
|
);
|
|
421
|
+
let emptyPageRetries = 0;
|
|
422
|
+
const MAX_EMPTY_PAGE_RETRIES = 10;
|
|
421
423
|
while (!backfillDone) {
|
|
422
|
-
const page = await
|
|
424
|
+
const page = await currentFetchBackfill({ startSlot, cursor });
|
|
423
425
|
if (!page.items.length && !page.cursor && !page.done) {
|
|
424
|
-
|
|
426
|
+
emptyPageRetries++;
|
|
427
|
+
if (emptyPageRetries > MAX_EMPTY_PAGE_RETRIES) {
|
|
428
|
+
this.logger.error(
|
|
429
|
+
`backfill returned ${MAX_EMPTY_PAGE_RETRIES} consecutive empty pages; treating as done`
|
|
430
|
+
);
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
const backoffMs = calculateBackoff(emptyPageRetries - 1, DEFAULT_RETRY_CONFIG);
|
|
434
|
+
this.logger.warn(
|
|
435
|
+
`empty backfill page without cursor; retrying in ${backoffMs}ms (${emptyPageRetries}/${MAX_EMPTY_PAGE_RETRIES})`
|
|
436
|
+
);
|
|
437
|
+
await delay(backoffMs);
|
|
425
438
|
continue;
|
|
426
439
|
}
|
|
440
|
+
emptyPageRetries = 0;
|
|
427
441
|
const sorted = [...page.items].sort(
|
|
428
442
|
(a, b) => compareBigint(extractSlot(a), extractSlot(b))
|
|
429
443
|
);
|
|
@@ -549,19 +563,20 @@ var ReplayStream = class {
|
|
|
549
563
|
async *miniBackfill(fromSlot, fetchBackfill, extractSlot, keyOf, seenItem, recordEmission) {
|
|
550
564
|
this.logger.info(`mini-backfill starting from slot ${fromSlot}`);
|
|
551
565
|
const MINI_BACKFILL_TIMEOUT = 3e4;
|
|
552
|
-
|
|
566
|
+
let lastProgressTime = Date.now();
|
|
553
567
|
let cursor;
|
|
554
568
|
let itemsYielded = 0;
|
|
555
569
|
try {
|
|
556
570
|
while (true) {
|
|
557
|
-
if (Date.now() -
|
|
558
|
-
this.logger.warn(`mini-backfill timed out after ${MINI_BACKFILL_TIMEOUT}ms`);
|
|
571
|
+
if (Date.now() - lastProgressTime > MINI_BACKFILL_TIMEOUT) {
|
|
572
|
+
this.logger.warn(`mini-backfill timed out after ${MINI_BACKFILL_TIMEOUT}ms with no progress`);
|
|
559
573
|
break;
|
|
560
574
|
}
|
|
561
575
|
const page = await fetchBackfill({ startSlot: fromSlot, cursor });
|
|
562
576
|
const sorted = [...page.items].sort(
|
|
563
577
|
(a, b) => compareBigint(extractSlot(a), extractSlot(b))
|
|
564
578
|
);
|
|
579
|
+
let pageYielded = 0;
|
|
565
580
|
for (const item of sorted) {
|
|
566
581
|
const slot = extractSlot(item);
|
|
567
582
|
const key = keyOf(item);
|
|
@@ -571,9 +586,11 @@ var ReplayStream = class {
|
|
|
571
586
|
}
|
|
572
587
|
recordEmission(slot, key);
|
|
573
588
|
itemsYielded++;
|
|
589
|
+
pageYielded++;
|
|
574
590
|
this.metrics.emittedReconnect += 1;
|
|
575
591
|
yield item;
|
|
576
592
|
}
|
|
593
|
+
if (pageYielded > 0) lastProgressTime = Date.now();
|
|
577
594
|
cursor = page.cursor;
|
|
578
595
|
if (page.done || cursor === void 0) break;
|
|
579
596
|
}
|
|
@@ -587,7 +604,12 @@ var ReplayStream = class {
|
|
|
587
604
|
};
|
|
588
605
|
async function safeClose(pump) {
|
|
589
606
|
try {
|
|
590
|
-
await
|
|
607
|
+
const result = await Promise.race([
|
|
608
|
+
pump.close().then(() => "closed"),
|
|
609
|
+
new Promise((resolve) => setTimeout(() => resolve("timeout"), 5e3))
|
|
610
|
+
]);
|
|
611
|
+
if (result === "timeout") {
|
|
612
|
+
}
|
|
591
613
|
} catch {
|
|
592
614
|
}
|
|
593
615
|
}
|
|
@@ -1074,6 +1096,7 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1074
1096
|
const streamBuffer = [];
|
|
1075
1097
|
let streamDone = false;
|
|
1076
1098
|
let streamError = null;
|
|
1099
|
+
let lastActivityTime = Date.now();
|
|
1077
1100
|
try {
|
|
1078
1101
|
cleanupTimer = setInterval(() => {
|
|
1079
1102
|
assembler.cleanup();
|
|
@@ -1083,6 +1106,7 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1083
1106
|
const streamProcessor = (async () => {
|
|
1084
1107
|
try {
|
|
1085
1108
|
for await (const response of stream) {
|
|
1109
|
+
lastActivityTime = Date.now();
|
|
1086
1110
|
const event = processResponseMulti(response, assembler);
|
|
1087
1111
|
if (event) {
|
|
1088
1112
|
if (event.type === "account") {
|
|
@@ -1172,6 +1196,7 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1172
1196
|
let retryAttempt = 0;
|
|
1173
1197
|
let currentStream = stream;
|
|
1174
1198
|
let currentStreamProcessor = streamProcessor;
|
|
1199
|
+
lastActivityTime = Date.now();
|
|
1175
1200
|
const createStreamProcessor = () => {
|
|
1176
1201
|
if (clientFactory) {
|
|
1177
1202
|
try {
|
|
@@ -1187,6 +1212,7 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1187
1212
|
try {
|
|
1188
1213
|
for await (const response of newStream) {
|
|
1189
1214
|
retryAttempt = 0;
|
|
1215
|
+
lastActivityTime = Date.now();
|
|
1190
1216
|
const event = processResponseMulti(response, assembler);
|
|
1191
1217
|
if (event) {
|
|
1192
1218
|
if (event.type === "account") {
|
|
@@ -1207,7 +1233,18 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1207
1233
|
return { stream: newStream, processor: newProcessor };
|
|
1208
1234
|
};
|
|
1209
1235
|
while (true) {
|
|
1236
|
+
const hadEvents = streamBuffer.length > 0;
|
|
1210
1237
|
yield* yieldStreamBuffer();
|
|
1238
|
+
if (hadEvents) {
|
|
1239
|
+
lastActivityTime = Date.now();
|
|
1240
|
+
}
|
|
1241
|
+
if (!streamDone && Date.now() - lastActivityTime > retryConfig.connectionTimeoutMs) {
|
|
1242
|
+
logger.warn(
|
|
1243
|
+
`[account-stream] no activity for ${retryConfig.connectionTimeoutMs}ms; forcing reconnection`
|
|
1244
|
+
);
|
|
1245
|
+
streamDone = true;
|
|
1246
|
+
streamError = new Error(`Operation timed out after ${retryConfig.connectionTimeoutMs}ms`);
|
|
1247
|
+
}
|
|
1211
1248
|
if (streamDone) {
|
|
1212
1249
|
if (streamError) {
|
|
1213
1250
|
const backoffMs = calculateBackoff(retryAttempt, retryConfig);
|
|
@@ -1219,6 +1256,7 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1219
1256
|
streamDone = false;
|
|
1220
1257
|
streamError = null;
|
|
1221
1258
|
streamBuffer.length = 0;
|
|
1259
|
+
lastActivityTime = Date.now();
|
|
1222
1260
|
const { stream: newStream, processor: newProcessor } = createStreamProcessor();
|
|
1223
1261
|
currentStream = newStream;
|
|
1224
1262
|
currentStreamProcessor = newProcessor;
|
|
@@ -1226,6 +1264,7 @@ async function* createAccountsByOwnerReplay(options) {
|
|
|
1226
1264
|
} else {
|
|
1227
1265
|
logger.warn("[account-stream] stream ended unexpectedly; reconnecting...");
|
|
1228
1266
|
streamDone = false;
|
|
1267
|
+
lastActivityTime = Date.now();
|
|
1229
1268
|
const { stream: newStream, processor: newProcessor } = createStreamProcessor();
|
|
1230
1269
|
currentStream = newStream;
|
|
1231
1270
|
currentStreamProcessor = newProcessor;
|