Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8335600

"java.io.IOException: Broken pipe" thrown where ClosedChannelException expected

    XMLWordPrintable

Details

    • generic
    • linux

    Description

      ADDITIONAL SYSTEM INFORMATION :
      multiple; tested on macOS 14.5 and Linux


      A DESCRIPTION OF THE PROBLEM :
      When using Unix domain sockets (JEP380 AF_UNIX), sun.nio.ch.SocketChannelImpl#write may under some circumstances throw a "java.io.IOException: Broken pipe" (or "java.io.IOException: Socket closed") where a java.nio.channels.ClosedChannelException is expected.

      The javadocs for SocketChannel#write state the following exceptions (note that IOException is not even documented here):
          /**
           * @throws NotYetConnectedException
           * If this channel is not yet connected
           * @throws ClosedChannelException {@inheritDoc}
           * @throws AsynchronousCloseException {@inheritDoc}
           * @throws ClosedByInterruptException {@inheritDoc}
           */
          public abstract int write(ByteBuffer src) throws IOException;

      The javadoc for the implemented interface WritableByteChannel state:
           * @throws NonWritableChannelException
           * If this channel was not opened for writing
           *
           * @throws ClosedChannelException
           * If this channel is closed
           *
           * @throws AsynchronousCloseException
           * If another thread closes this channel
           * while the write operation is in progress
           *
           * @throws ClosedByInterruptException
           * If another thread interrupts the current thread
           * while the write operation is in progress, thereby
           * closing the channel and setting the current thread's
           * interrupt status
           *
           * @throws IOException
           * If some other I/O error occurs

      The conditions "socket closed" and "broken pipe" are both indicative of a closed channel and are therefore not just "some other I/O error".

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Start a virtual thread ("server thread") opening a server socket channel for a local UnixDomainSocketAddress, accepting connections and immediately closing them.
      2. Start another virtual thread ("write thread"), connecting to that server, trying to write a byte.
      3. In the main thread, interrupt the write thread, either immediately or after a longer delay (long enough for the server-side to close the connection)
      4. Observe the thrown exceptions, compare the cases of no-delay vs. delay.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      In all cases, a ClosedChannelException (or a subclass thereof) should be thrown (AsynchronousCloseException or ClosedByInterruptException), depending on the exact concurrency of operations.

      It is also acceptable to have the write succeed (which just means the kernel has buffered the write somewhere even if it is never read by the server thread).

      ACTUAL -
      no delay: Fails with either ClosedChannelException, AsynchronousCloseException or ClosedByInterruptException (all acceptable)
      delay: Fails with either "java.io.IOException: Broken pipe" or (rarely) "java.io.IOException: Socket not connected"

      The "java.io.IOException" is not thrown when replacing the UNIX domain socket code by code making use of regular AF_INET sockets (i.e., SocketChannel.open() and InetSocketAddress)

      Source code outputs (from Java 21, also with Java 22)

      Running with delay: false
      java.nio.channels.ClosedChannelException
      at java.base/sun.nio.ch.SocketChannelImpl.ensureOpenAndConnected(SocketChannelImpl.java:222)
      at java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:536)
      at org.newsclub.net.unix/org.newsclub.net.unix.ExceptionBugTest.lambda$1(ExceptionBugTest.java:57)
      at java.base/java.lang.VirtualThread.run(VirtualThread.java:309)
      Running with delay: true
      java.io.IOException: Broken pipe
      at java.base/sun.nio.ch.SocketDispatcher.write0(Native Method)
      at java.base/sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:62)
      at java.base/sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:137)
      at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:102)
      at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:58)
      at java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:542)
      at org.newsclub.net.unix/org.newsclub.net.unix.ExceptionBugTest.lambda$1(ExceptionBugTest.java:57)
      at java.base/java.lang.VirtualThread.run(VirtualThread.java:309)

      Alternative 1:
      Running with delay: false
      java.nio.channels.AsynchronousCloseException
      at java.base/java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:203)
      at java.base/sun.nio.ch.SocketChannelImpl.endWrite(SocketChannelImpl.java:527)
      at java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:550)
      at org.newsclub.net.unix/org.newsclub.net.unix.ExceptionBugTest.lambda$1(ExceptionBugTest.java:57)
      at java.base/java.lang.VirtualThread.run(VirtualThread.java:309)

      Alternative 2:
      Running with delay: false
      java.nio.channels.ClosedByInterruptException
      at java.base/java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:200)
      at java.base/sun.nio.ch.SocketChannelImpl.endWrite(SocketChannelImpl.java:527)
      at java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:550)
      at org.newsclub.net.unix/org.newsclub.net.unix.ExceptionBugTest.lambda$1(ExceptionBugTest.java:57)
      at java.base/java.lang.VirtualThread.run(VirtualThread.java:309)

      ---------- BEGIN SOURCE ----------
      import java.io.IOException;
      import java.net.StandardProtocolFamily;
      import java.net.UnixDomainSocketAddress;
      import java.nio.ByteBuffer;
      import java.nio.channels.ServerSocketChannel;
      import java.nio.channels.SocketChannel;
      import java.nio.file.Files;
      import java.nio.file.Path;
      import java.util.concurrent.CountDownLatch;


      public class ExceptionBugTest {
        public static void main(String[] args) throws Exception {
          test(false);
          test(true);
        }

        private static void test(boolean delay) throws Exception {
          System.err.flush();
          System.out.println("Running with delay: " + delay);
          System.out.flush();

          Path socketPath = Files.createTempFile("bug", "test");
          Files.delete(socketPath);
          UnixDomainSocketAddress addr = UnixDomainSocketAddress.of(socketPath);
          try {
            @SuppressWarnings("resource")
            var serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
            serverChannel.bind(addr);

            CountDownLatch threadsReady = new CountDownLatch(2);
            Thread serverThread = Thread.ofVirtual().start(() -> {
              threadsReady.countDown();

              try (SocketChannel channel = serverChannel.accept()) {
                // close immediately
              } catch (Exception e) {
                e.printStackTrace();
              }
            });

            try (SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX)) {
              channel.connect(addr);

              Thread writeThread = Thread.ofVirtual().start(() -> {
                try {
                  threadsReady.countDown();
                  int written = channel.write(ByteBuffer.allocate(1));

                  // occasionally succeeds
                  System.out.println("write returned without exception: " + written
                      + " (please re-run test)");
                } catch (IOException e) {
                  // delay=false:
                  // will usually fail with either ClosedChannelException, AsynchronousCloseException,
                  // or ClosedByInterruptException
                  //
                  // delay=true:
                  // will usually fail with "java.io.IOException: Broken pipe"
                  // but also sometimes with "java.io.IOException: Socket is not connected"
                  e.printStackTrace();
                }
              });

              threadsReady.await();
              if (delay) {
                Thread.sleep(500);
              }

              // serverThread.interrupt();
              writeThread.interrupt();
            } finally {
              try {
                serverChannel.close();
              } catch (IOException e) {
                e.printStackTrace();
              }
              serverThread.join();
            }

          } finally {
            Files.deleteIfExists(socketPath);
          }
        }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :



      FREQUENCY : always


      Attachments

        Activity

          People

            michaelm Michael McMahon
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

              Created:
              Updated: