Skip to content

fix(client): swallow synchronous EPIPE from writeAfterFIN (#3282)#3283

Merged
nkaradzhov merged 3 commits into
redis:masterfrom
nkaradzhov:fix-3282-writeAfterFIN
May 20, 2026
Merged

fix(client): swallow synchronous EPIPE from writeAfterFIN (#3282)#3283
nkaradzhov merged 3 commits into
redis:masterfrom
nkaradzhov:fix-3282-writeAfterFIN

Conversation

@nkaradzhov
Copy link
Copy Markdown
Collaborator

@nkaradzhov nkaradzhov commented May 18, 2026

net.Socket.write can throw synchronously with code 'EPIPE' on a half-closed socket, before the 'close' event fires. The throw escaped RedisSocket.write from a setImmediate flush and crashed the process. Preflight writable, wrap the cork/write loop in try/finally so uncork always runs, and narrowly swallow EPIPE — the close handler will reject the pending command via #waitingForReply.

Fixes #3282

Description

Describe your pull request here


Checklist

  • Does npm test pass with this change (including linting)?
  • Is the new or changed code fully tested?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?

Note

Medium Risk
Medium risk because it changes low-level socket write behavior and error handling; mistakes could hide real network failures or affect backpressure/flush behavior under load.

Overview
Prevents process crashes when net.Socket.write throws synchronously on half-closed connections by guarding RedisSocket.write with a writable check, ensuring uncork() always runs via try/finally, and swallowing only EPIPE while rethrowing other errors.

Adds targeted unit tests that capture the underlying net.Socket to verify: no writes occur when the socket is not writable, synchronous EPIPE is ignored, and non-EPIPE errors still surface.

Reviewed by Cursor Bugbot for commit 1452cac. Bugbot is set up for automated code reviews on this repo. Configure here.

net.Socket.write can throw synchronously with code 'EPIPE' on a
half-closed socket, before the 'close' event fires. The throw escaped
RedisSocket.write from a setImmediate flush and crashed the process.
Preflight `writable`, wrap the cork/write loop in try/finally so
uncork always runs, and narrowly swallow EPIPE — the close handler
will reject the pending command via #waitingForReply.

Fixes redis#3282

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nkaradzhov and others added 2 commits May 18, 2026 17:17
Replace `any` casts with `unknown` and rename the unused `err`
parameter to `_err` so `lint:changed` passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The write tests in redis#3282 left the underlying TCP connection alive,
which made server.close() wait forever and tripped Mocha's 2000ms
timeout on CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nkaradzhov nkaradzhov merged commit 47edb81 into redis:master May 20, 2026
14 checks passed
@nkaradzhov nkaradzhov deleted the fix-3282-writeAfterFIN branch May 20, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RedisSocket.write throws synchronously on writeAfterFIN, causing unhandled rejection (v5.12.1)

2 participants