Skip to content

test(console-write): accept ENOTCONN on macOS when the reader hangs up - #44072

Merged
Jarred-Sumner merged 1 commit into
mainfrom
robobun/2983ca60/console-write-enotconn-macos
Sep 27, 2026
Merged

Jarred-Sumner merged 1 commit into
mainfrom
robobun/2983ca60/console-write-enotconn-macos

Conversation

@robobun

@robobun robobun commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Fix

  • On macOS the case accepts caught EPIPE or caught ENOTCONN. Other platforms accept caught EPIPE only.
  • The case still requires a rejected promise, no other stderr output, and exit code 0.
  • Verified on a CI Mac (macOS 26.6.1, M2), main canary, 150 runs of the file. caught ENOTCONN fails 7 runs of the old file and 0 runs of the new file.
  • bun bd test test/js/bun/console/console-write.test.ts passes on Linux.

Background

Notes

This PR changes one test. No file under src/ changes.

Kernel path (xnu-12377, bsd/kern):

  • sosendcheck() (uipc_socket.c) tests SS_CANTSENDMORE first and returns EPIPE.
  • sosend() calls socket_unlock() before the uiomove copy and socket_lock() after it. It calls pru_send with no second state test.
  • uipc_send() (uipc_usrreq.c, SOCK_STREAM branch) tests SS_ISCONNECTED first (ENOTCONN) and SS_CANTSENDMORE second (EPIPE).
  • soisdisconnected() (uipc_socket2.c) clears SS_ISCONNECTED and sets SS_CANTSENDMORE in one step.
  • write(2) on a socket takes the same path (soo_write() calls sosend()).

C probe, no Bun and no Node. An 8 MB write on an AF_UNIX socketpair with 512 KB buffers. The peer reads one chunk and closes. 500 runs each.

host macOS non-blocking send() blocking write()
M2 mini 26.6.1 arm64 ENOTCONN 141 ENOTCONN 396
M2 Ultra 15.7.9 arm64 ENOTCONN 442 ENOTCONN 392
Intel i7 14.8.9 x64 ENOTCONN 1 ENOTCONN 287
Linux x64 ENOTCONN 0 of 300 ENOTCONN 0 of 200

All other runs gave EPIPE. So the errno is not specific to macOS 26 or to one machine. With the default socket buffer size the M2 mini gave EPIPE in 500 of 500 runs.

One more write directly after the ENOTCONN returned EPIPE in 1659 of 1659 runs. A socket that was never connected returns ENOTCONN on each write.

The scenario of the test, on the M2 mini (macOS 26.6.1), Bun 1.4.3-canary.1+37da174d5, Node v26.3.0:

parent child EPIPE ENOTCONN
bun bun, await console.write(big) 63 137
bun bun, process.stdout.write(big, cb) 66 84
bun node, process.stdout.write(big, cb) 145 55
node node, process.stdout.write(big, cb) 85 65

On macOS 14.8.9 x64, node parent and node child: ENOTCONN in 18 of 200 runs.

Runs of the test file on the M2 mini:

binary test file runs failed failures
main canary main 150 76 7 caught ENOTCONN (one argument), 69 unhandled rejection (several arguments)
main canary this PR 150 67 67 unhandled rejection (several arguments)
#43679 (4133870) #43679 100 3 3 caught ENOTCONN (one argument)
#43679 (4133870) #43679 merged with this PR 100 0

The test on main stays flaky on macOS until #43679 merges.

The decision in #40935. An earlier revision of #40935 folded ENOTCONN into EPIPE in write_to_socket. The merged commit (118fdd2) dropped the fold: "The kernel errno is passed through unchanged." Node accepts both codes on macOS in its own test (test/js/node/test/parallel/test-cluster-concurrent-disconnect.js:27-33). At that time the race was not reproduced (1100 runs, all EPIPE). The probes above reproduce it. This PR keeps the decision.

process.platform and not isMacOS. #43679 changes the harness import line of this file. The test reads process.platform so that the two PRs merge in either order with no conflict (checked with git merge-tree).

The same errno in other tests. In the annotations of the last 60 finished builds, ENOTCONN appears in the output of three test files: this one, test/js/bun/util/filesink.test.ts (#43794 removes the race there) and test/js/node/process/process-stdin.test.ts (#42260 ignores the code on macOS).


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/console/console-write.test.ts

The macOS kernel fails a write to a socket with ENOTCONN, not EPIPE,
when the peer closes while that write copies its bytes in. Every later
write fails with EPIPE. Bun passes the kernel errno through, as Node
does (#40935).

The case "an awaited console.write rejects with EPIPE when the reader
has hung up" closes the reader while the child writes, so on macOS the
child can catch either code. The test accepts both there and still
requires that the promise rejects and that nothing else reaches stderr.
@robobun

robobun commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:16 PM PT - Sep 26th, 2026

❌ @robobun, your commit d43b6b7 has 2 failures in Build #121049 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 44072

That installs a local version of the PR into your bun-44072 executable, so you can run:

bun-44072 --bun

@robobun

robobun commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review. This PR changes one test and no file under src/.

How I reproduced the failure:

  • On a CI Mac (macOS 26.6.1, M2) with the main canary (1.4.3-canary.1+37da174d5), I ran bun test test/js/bun/console/console-write.test.ts 150 times. 7 runs fail in the one argument case with - "caught EPIPE / + "caught ENOTCONN. With the test from this PR, 0 runs fail that way.
  • A C program with no Bun and no Node gets the same errno. It writes 8 MB to an AF_UNIX socketpair with 512 KB buffers, and the peer reads one chunk and closes. The write fails with ENOTCONN in 141 of 500 runs and with EPIPE in the rest. On Linux it fails with EPIPE in 300 of 300 runs.

The several arguments case has a second failure on main, an unhandled rejection. #43679 fixes it. The file is green on macOS when both PRs are in.

CI, build 121049: the changed test passes on each lane that ran it, darwin aarch64 and darwin x64 included. The build is red on two tests that this PR does not touch:

PR: #44072

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: oven-sh/bun/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 2e9e6fbd-d45a-4d4d-9dec-4320fe0b1633

📥 Commits

Reviewing files that changed from the base of the PR and between 36cd151 and d43b6b7.

📒 Files selected for processing (1)
  • test/js/bun/console/console-write.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.


Walkthrough

The hung-up-reader test now accepts ENOTCONN as an alternative to EPIPE on macOS. On other platforms, it still expects EPIPE.

Changes

Console write test

Layer / File(s) Summary
Platform-specific error expectation
test/js/bun/console/console-write.test.ts
The hung-up-reader test accepts ENOTCONN or EPIPE on macOS. Other platforms still require EPIPE.

Suggested reviewers: dylan-conway

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to d43b6

The test accepts either reported macOS write error while retaining its existing failure and output checks. No actionable merge risk remains.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: accepting ENOTCONN on macOS in the console-write test.
Description check ✅ Passed The description explains the problem, fix, platform-specific behavior, verification results, and relevant background. It provides the information requested by the template, although it uses Problem an…

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review completed

Nothing new to post: everything this review found is already covered by existing comments on this pull request or didn't merit a separate one.

@Jarred-Sumner
Jarred-Sumner merged commit 91c3182 into main Sep 27, 2026
5 of 7 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the robobun/2983ca60/console-write-enotconn-macos branch September 27, 2026 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants