@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 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 fetchBackfill({ startSlot, cursor });
425
+ const page = await currentFetchBackfill({ startSlot, cursor });
424
426
  if (!page.items.length && !page.cursor && !page.done) {
425
- this.logger.warn("empty backfill page without cursor; retrying");
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
- const startTime = Date.now();
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() - startTime > MINI_BACKFILL_TIMEOUT) {
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 pump.close();
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;