fix(worker): harden BullMQ Redis connections against failover crash (#1291) - #1303
Open
saidai-bhuvanesh wants to merge 1 commit into
Open
fix(worker): harden BullMQ Redis connections against failover crash (#1291)#1303saidai-bhuvanesh wants to merge 1 commit into
saidai-bhuvanesh wants to merge 1 commit into
Conversation
…itya-003#1291) A transient Redis disconnect/failover emitted an 'error' event on the ioredis connection backing the BullMQ Worker/Queue/QueueEvents with no listener, so Node threw an unhandled 'error' and the worker process crashed ('Connection is closed.'), halting all background transaction processing. - config/redis.js: attachConnectionHandlers() attaches error/close/ reconnecting/connect/ready listeners to every queue connection (and pub/sub duplicate()) so connection errors are logged+absorbed and ioredis keeps retrying via retryStrategy instead of killing the process. Idempotent (guards against duplicate listeners). - blockchainWorker.js: installProcessErrorGuards() adds process-level unhandledRejection/uncaughtException handlers that absorb transient connection errors while keeping genuine fatal errors fatal. Installed at the start of initializeWorker(). - blockchainQueue.js: harden the QueueEvents connection; also remove a stray parse-breaking '.catch(...)' fragment at EOF that made the module un-loadable (target file per Nitya-003#1291). - tests: blockchainWorkerRedisResilience.test.js (4 tests) verifies an emitted 'error'/'close'/'reconnecting' does not throw, idempotent attachment, null-safety, and pub/sub duplicate() hardening. Closes Nitya-003#1291
|
@openhands-agent is attempting to deploy a commit to the Nitya Gosain's projects Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A transient Redis failure (socket disconnect, failover,
ECONNREFUSED) crashed the BullMQ blockchain worker process instead of recovering. This PR hardens every BullMQ/ioredis connection so connection errors are logged and absorbed, and ioredis keeps retrying via its ownretryStrategy— the worker stays alive and resumes processing once Redis returns.Problem
backend/services/blockchainWorker.jscreated the worker's Redis connection viacreateQueueConnection()(backend/config/redis.js) and BullMQ'sWorker/Queue/QueueEventsemit low-level ioredis connection failures as'error'events on the connection object itself. In Node, an EventEmitter that emits'error'with no listener throws// Unhandled 'error' event, which — when it escapes a promise — surfaces as anUnhandledPromiseRejectionErrorand terminates the process.Reproducing the issue's steps (enqueue jobs, then
docker restart redis): with no'error'listener on the connection, the worker died with:All background blockchain transaction processing (batch creation minting, stage updates) halted for the whole server instance until a manual restart, and any in-flight jobs were not retried cleanly.
Fix
1. Connection-level error absorption (root cause) —
config/redis.jsNew
attachConnectionHandlers(connection)attacheserror,close,reconnecting,connect, andreadylisteners to an ioredis connection. Theerrorlistener logs and absorbs the event so it can never become an unhandled exception;iorediscontinues to retry per the existingretryStrategy(exponential backoff, capped).createQueueConnection()now hardens every connection it creates, andcreatePubSubClients()hardens both the pub and theduplicate()d sub client. Attachment is idempotent (a__cropchainResilienceAttachedflag prevents stacking duplicate listeners on aduplicate()or re-call).2. Process-level guards —
blockchainWorker.jsNew idempotent
installProcessErrorGuards()(called at the start ofinitializeWorker()) installsprocess.on('unhandledRejection')andprocess.on('uncaughtException')handlers that detect connection-class errors (connection is closed,ECONNREFUSED,ECONNRESET,ETIMEDOUT,redis,bullmq,unhandled 'error' event) and log+suppress them, while still letting genuine non-connection fatal errors propagate. This is defense-in-depth for any error that escapes the connection listener (e.g. inside a BullMQ internalWorker.retrypromise).3. QueueEvents hardening —
blockchainQueue.jsinitializeQueue()previously created theQueueEventsconnection inline without hardening; it now goes throughcreateQueueConnection()+attachConnectionHandlers(), so a failover on the events stream no longer crashes the process either. ThequeueEvents.on("error", ...)handler is retained.4. Removed a stray parse-breaking fragment —
blockchainQueue.jsThe module had a trailing
.catch(err => console.error("Promise.all failed:", err));at module scope (no preceding expression), which is a hardSyntaxErrorthat made the entire module un-loadable via Babel/Node — directly breaking the worker's import ofQUEUE_NAMES/JOB_TYPES. Removed so the module loads. (blockchainQueue.jsis a target file of this issue.)Verification
backend/tests/blockchainWorkerRedisResilience.test.js(4 tests, all passing): assertscreateQueueConnection()attacheserror/close/reconnectinglisteners and that emittingerror/close/reconnectingdoes not throw; idempotent attachment (no duplicate listeners on repeated calls); null/undefined safety; and pub/subduplicate()hardening (aduplicate()has zero listeners untilattachConnectionHandlersis applied, after which an emittederrorno longer throws).npx jest tests/blockchainWorkerRedisResilience.test.js→ 4/4 passing. The run's logs visibly show connection errors being logged ([Redis] Connection error (recovering): ...) rather than thrown.config/redis.jsandblockchainQueue.jsload cleanly undernode -e require(...);services/blockchainWorker.jspassesnode --check.SyntaxErrors from an unrelated stray-line corruption inmodels/Batch.js,controllers/batchController.js, andservices/batchService.js(trailing.then(/.catch(...)fragments at EOF) — none caused by this change, and none in the files this PR touches. Those should be addressed in a separate PR.Behaviour preserved
retryStrategy; connection config is unchanged.maxRetriesPerRequest: nullfor BullMQ connections is preserved.worker.on("error"),worker.on("completed"),worker.on("failed"),worker.on("stalled")are unchanged.Changes
backend/config/redis.js— addattachConnectionHandlers(), apply increateQueueConnection()andcreatePubSubClients(); export it.backend/services/blockchainWorker.js— addinstallProcessErrorGuards(), call it frominitializeWorker().backend/services/blockchainQueue.js— harden theQueueEventsconnection; remove stray parse-breaking trailing fragment.backend/tests/blockchainWorkerRedisResilience.test.js— new regression tests (4 tests).Closes #1291