Non-blocking pipes still appear to be isBlocking

Charles Oliver Nutter headius at headius.com
Wed Jan 31 00:35:35 UTC 2024


On Tue, Jan 30, 2024 at 1:12 AM Alan Bateman <Alan.Bateman at oracle.com> wrote:
> The source/sink ends of a Pipe are selectable channels and can therefore be configured non-blocking. I wonder if the user's code chokes with usages like this:
>
> pipe.source().configureBlocking(false);
> pipe.sink().configureBlocking(false);

The user's code is actually in the child process, expecting that the
child stream will be configured as blocking. The issue is that when
you create a pipe using java.nio.channels.Pipe, both ends are secretly
set non-blocking at the native level, but both appear to be blocking
at a Java level.

JRuby's subprocess logic uses posix_spawn directly, and we set up the
pipes for it. In order to maintain the old behavior, where the child
ends of the pipes are blocking, we'll have to clear the O_NONBLOCK
bit... but in order to do that, we have to set the Java channel as
nonblocking {so its AbstractSelectableChannel.nonblock flag gets set)
and then set it to blocking (now that the nonblock flag is set, it
will clear it and proceed to the native fcntl call to remove
O_NONBLOCK).

This is pretty cumbersome.

> As regards the underlying blocking mode: It's okay for a SelectableChannel to implement blocking semantics when then underlying socket/fifo is configured non-blocking.

JRuby's IO has worked this way for over a decade, since we needed
blocking IO operations to be interruptible without destroying the
channel (NIO will just close the channel if a blocking IO call is
interrupted).

> Mostly they are the equal, meaning if the SelectableChannel blocking mode is true then the underlying socket/fifo's blocking mode is also true. For the network channels, the first use of a virtual thread will change the channel's socket to be non-blocking. The pipe channels are a  bit different in that they eagerly change the underlying fifo to be non-blocking. Both are valid approaches as it's transparent to anything using the API. The pipe channel could be changed to work like the network channels and change it lazily, I think they were only done this week for expediency rather than anything else.

To be clear, I don't have any real problem with setting O_NONBLOCK for
the streams consumed by JVM code. Ruby's recent push for structured
concurrency with fibers (like vthreads) has led them to do the same
thing: O_NONBLOCK at a native level, but simulated blocking at a
user-facing API level. JRuby has largely worked this way for years.
But in CRuby, the child side of the pipes are *not* set nonblocking.
In order to maintain compatibility, we're going to need to reset
blocking status once we know a channel is going to the child. That
means performing this ugly set+clear operation some time before we
call posix_spawn.

>
> -Alan


More information about the loom-dev mailing list