Skip to content

Yield socket pairs to a block and close them afterwards - #9750

Merged
headius merged 2 commits into
jruby:masterfrom
sampokuokkanen:socket-pair-block
Oct 1, 2026
Merged

headius merged 2 commits into
jruby:masterfrom
sampokuokkanen:socket-pair-block

Conversation

@sampokuokkanen

Copy link
Copy Markdown
Contributor

Socket.pair and UNIXSocket.pair ignored a block, so code written for MRI's rsock_s_socketpair never ran the block and leaked the sockets. Yield the pair, return the block's value, and close the second socket and then the first afterwards, rescuing StandardError from each close and restoring $! like ext/socket's io_close.

MRI tests that use the block form now actually run. Exclude the ones that fail on gaps elsewhere: EPIPE after shutdown(SHUT_WR), send_io, sendmsg_nonblock, and closing an IO while another fiber waits on it through the fiber scheduler, which hangs because close does not call the scheduler's fiber_interrupt.

Reason for this PR: I want to fix test_io_close_across_fibers, but first we need to get it failing properly.

Socket.pair and UNIXSocket.pair ignored a block, so code written for
MRI's rsock_s_socketpair never ran the block and leaked the sockets.
Yield the pair, return the block's value, and close the second socket
and then the first afterwards, rescuing StandardError from each close
and restoring $! like ext/socket's io_close.

MRI tests that use the block form now actually run. Exclude the ones
that fail on gaps elsewhere: EPIPE after shutdown(SHUT_WR), send_io,
sendmsg_nonblock, and closing an IO while another fiber waits on it
through the fiber scheduler, which hangs because close does not call
the scheduler's fiber_interrupt.
Now that UNIXSocket.pair runs its block, this test runs too. On Linux a
thread blocked reading the IO sees EOF, or stays blocked, when another
thread closes it, instead of raising IOError as MRI does.
@sampokuokkanen

sampokuokkanen commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor Author

test_io_close_blocking_thread passes on Mac and fails on Linux. "Passes" on master because the block is ignored there.

private static void closeRescued(ThreadContext context, IRubyObject io) {
IRubyObject errorInfo = context.getErrorInfo();
try {
io.callMethod(context, "close");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

does this really need to be dynamic? given these seems to be of RubyBasicSocket type...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IO.pipe's block form seems to do it like this: https://github.com/jruby/jruby/blob/61230eb921/core/src/main/java/org/jruby/RubyIO.java#L2367.

I think it's needed here since you can override close.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CRuby also dynamically invokes close for its ensure around the yield.

https://github.com/ruby/ruby/blob/b9a50a0b81a1befe0ab02bd93f01a9e684c55149/ext/socket/socket.c#L156-L167

@headius headius left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh, disappointing to find specs that fail simply because a block was never called. Glad you stumbled onto this.

@headius headius modified the milestones: JRuby 10.0.8.0, JRuby 10.1.3.0 Oct 1, 2026
@headius
headius merged commit b23c63b into jruby:master Oct 1, 2026
133 of 135 checks passed
@headius

headius commented Oct 1, 2026

Copy link
Copy Markdown
Member

This could probably backport to 10.0 but I'm a little amazed nobody has reported this as a problem before. This is how socketpair has behaved since at least 2009. 😳

@sampokuokkanen

Copy link
Copy Markdown
Contributor Author

I looked into how much this block form is used and it seems to be mainly used in tests, like in MRI tests and also by io-event (also in tests). So probably ok not to backport. 😅

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.

3 participants