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