fix: S3→Telegram upload pipeline — OOM, queue limits, shutdown drain, timeouts
Deploy FileDrop / deploy (push) Successful in 46s
Deploy FileDrop / deploy (push) Successful in 46s
CRITICAL: - Content-MD5 no longer loads entire file via arrayBuffer() — MD5 computed incrementally in streamBodyToTemp alongside SHA-256 (fixes OOM for GB files) HIGH: - Add 120s timeout to Telegraf API calls via Promise.race in executeWithBotRetry (prevents queue slot exhaustion from hung Telegram connections) - Add queue size limit (1000 pending max) — reject new tasks when full - Add graceful shutdown drain — waitForQueue with 30s timeout before exit - Fix temp file leak when findFileByBucketAndKey throws (wrap in try-catch) - Fix createReadStream fd leak — destroy stream on forwardToStorage error - writer.end() wrapped in silent try-catch to prevent error swallowing - writer.end() result ignored, writerFailed flag prevents double-end MEDIUM: - Remove 'retry after' from isTransientError patterns to stop double-retry layering (was causing up to 96 bot attempts per chunk) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,7 +42,9 @@ const sleep = (ms: number): Promise<void> => {
|
||||
const isTransientError = (error: unknown): boolean => {
|
||||
const str = error instanceof Error ? error.message : String(error);
|
||||
const transientPatterns = [
|
||||
'retry after',
|
||||
// 'retry after' is deliberately omitted — 429 is handled by
|
||||
// executeWithBotRetry at a deeper layer. Including it here would
|
||||
// cause double-retry (up to 96 attempts per chunk).
|
||||
'timeout',
|
||||
'Timed out',
|
||||
'etimedout',
|
||||
@@ -76,6 +78,12 @@ const isTransientError = (error: unknown): boolean => {
|
||||
*/
|
||||
const MAX_TRANSIENT_RETRIES = 3;
|
||||
|
||||
/**
|
||||
* Timeout in milliseconds for individual Telegram API calls.
|
||||
* 120 seconds to accommodate large document uploads.
|
||||
*/
|
||||
const TELEGRAM_API_TIMEOUT_MS = 120_000;
|
||||
|
||||
/**
|
||||
* Manages a pool of Telegram bots with automatic rotation and rate-limit handling.
|
||||
*
|
||||
@@ -129,7 +137,17 @@ export class BotPool implements ITelegramService {
|
||||
const currentBot = this.bots[botIndex];
|
||||
const currentToken = this.botTokens[botIndex];
|
||||
try {
|
||||
return await action(currentBot, currentToken);
|
||||
// Add timeout to prevent hung API calls from occupying queue slots
|
||||
const result = await Promise.race([
|
||||
action(currentBot, currentToken),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error(`Telegram API timeout after ${TELEGRAM_API_TIMEOUT_MS}ms`)),
|
||||
TELEGRAM_API_TIMEOUT_MS,
|
||||
),
|
||||
),
|
||||
]);
|
||||
return result;
|
||||
} catch (error: unknown) {
|
||||
const errorStr = error instanceof Error ? error.message : String(error);
|
||||
const match = errorStr.match(/retry after (\d+)/i);
|
||||
|
||||
Reference in New Issue
Block a user