8198372: API to create a SelectableChannel to a FileDescriptor
Reviewed-by: chegar, bpb
--- a/src/java.base/share/classes/module-info.java Sat Jun 23 08:03:52 2018 +0100
+++ b/src/java.base/share/classes/module-info.java Sat Jun 23 09:34:09 2018 +0100
@@ -242,6 +242,7 @@
exports sun.nio.ch to
java.management,
jdk.crypto.cryptoki,
+ jdk.net,
jdk.sctp,
jdk.unsupported;
exports sun.nio.cs to
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.net/share/classes/jdk/nio/Channels.java Sat Jun 23 09:34:09 2018 +0100
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nio;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.spi.AbstractSelectableChannel;
+import java.nio.channels.spi.SelectorProvider;
+import java.util.Objects;
+
+import sun.nio.ch.IOUtil;
+import sun.nio.ch.Net;
+import sun.nio.ch.SelChImpl;
+import sun.nio.ch.SelectionKeyImpl;
+import sun.nio.ch.SelectorProviderImpl;
+
+/**
+ * Defines static methods to create {@link java.nio.channels.Channel channels}.
+ *
+ * <p> Unless otherwise specified, passing a {@code null} argument to any of the
+ * methods defined here will cause a {@code NullPointerException} to be thrown.
+ *
+ * @since 11
+ */
+
+public final class Channels {
+ private Channels() { }
+
+ /**
+ * An object used to coordinate the closing of a selectable channel created
+ * by {@link Channels#readWriteSelectableChannel readWriteSelectableChannel}.
+ *
+ * @since 11
+ */
+ public interface SelectableChannelCloser {
+
+ /**
+ * Closes a selectable channel.
+ *
+ * <p> This method is invoked by the channel's close method in order to
+ * perform the actual work of closing the channel. This method is only
+ * invoked if the channel has not yet been closed, and it is never
+ * invoked more than once by the channel's close implementation.
+ *
+ * <p> An implementation of this method must arrange for any other
+ * thread that is blocked in an I/O operation upon the channel to return
+ * immediately, either by throwing an exception or by returning normally.
+ * If the channel is {@link SelectableChannel#isRegistered registered}
+ * with one or more {@link java.nio.channels.Selector Selector}s then
+ * the file descriptor should not be released until the {@link
+ * #implReleaseChannel implReleaseChannel} method is invoked. </p>
+ *
+ * @param sc
+ * The selectable channel
+ *
+ * @throws IOException
+ * If an I/O error occurs while closing the file descriptor
+ *
+ * @see java.nio.channels.spi.AbstractInterruptibleChannel#implCloseChannel
+ */
+ void implCloseChannel(SelectableChannel sc) throws IOException;
+
+ /**
+ * Release the file descriptor and any resources for a selectable
+ * channel that closed while registered with one or more {@link
+ * java.nio.channels.Selector Selector}s.
+ *
+ * <p> This method is for cases where a channel is closed when
+ * {@link java.nio.channels.SelectableChannel#isRegistered registered}
+ * with one or more {@code Selector}s. A channel may remain registered
+ * for some time after it is closed. This method is invoked when the
+ * channel is eventually deregistered from the last {@code Selector}
+ * that it was registered with. It is invoked at most once.
+ *
+ * @apiNote This method is invoked while synchronized on the selector
+ * and its selected-key set. Great care must be taken to avoid deadlocks
+ * with other threads that also synchronize on these objects.
+ *
+ * @param sc
+ * The closed selectable channel
+ *
+ * @throws IOException
+ * If an I/O error occurs
+ *
+ * @see java.nio.channels.spi.AbstractSelector#deregister
+ */
+ void implReleaseChannel(SelectableChannel sc) throws IOException;
+ }
+
+ /**
+ * Creates a selectable channel to a file descriptor that supports an
+ * {@link SelectableChannel#validOps() operation-set} of
+ * {@link SelectionKey#OP_READ OP_READ} and
+ * {@link SelectionKey#OP_WRITE OP_WRITE}. The selectable channel will be
+ * created by the default {@link SelectorProvider}.
+ *
+ * <p> The given file descriptor is a socket or resource that can be
+ * multiplexed by a {@link java.nio.channels.Selector} for read and write
+ * readiness. Great care is required to coordinate direct use of the file
+ * descriptor with the use of the selectable channel. In particular,
+ * changing the blocking mode or closing the file descriptor without careful
+ * coordination will result in unspecified and unsafe side effects. The
+ * given {@link SelectableChannelCloser SelectableChannelCloser} is invoked to
+ * close the file descriptor and to coordinate the closing when the channel
+ * is registered with a {@code Selector}. </p>
+ *
+ * <p> If there is a security manager set then its
+ * {@link SecurityManager#checkRead(FileDescriptor) checkRead} and
+ * {@link SecurityManager#checkWrite(FileDescriptor) checkWrite} methods
+ * are invoked to check that the caller has permission to both read from and
+ * write to the file descriptor. </p>
+ *
+ * @implNote This method throws {@code UnsupportedOperationException} if
+ * the default {@code SelectorProvider} is not the JDK built-in implementation.
+ *
+ * @param fd
+ * The file descriptor
+ * @param closer
+ * The object to close the channel
+ *
+ * @return The selectable channel
+ *
+ * @throws IllegalArgumentException
+ * If the file descriptor is not {@link FileDescriptor#valid() valid}
+ * @throws SecurityException
+ * If denied by the security manager
+ */
+ public static SelectableChannel readWriteSelectableChannel(FileDescriptor fd,
+ SelectableChannelCloser closer) {
+ Objects.requireNonNull(closer);
+ if (!fd.valid())
+ throw new IllegalArgumentException("file descriptor is not valid");
+
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkRead(fd);
+ sm.checkWrite(fd);
+ }
+
+ SelectorProvider provider = SelectorProvider.provider();
+ if (!(provider instanceof SelectorProviderImpl))
+ throw new UnsupportedOperationException("custom SelectorProvider");
+
+ return new ReadWriteChannelImpl((SelectorProviderImpl)provider, fd, closer);
+ }
+
+ private static final class ReadWriteChannelImpl
+ extends AbstractSelectableChannel implements SelChImpl
+ {
+ private final FileDescriptor fd;
+ private final int fdVal;
+ private final SelectableChannelCloser closer;
+
+ ReadWriteChannelImpl(SelectorProviderImpl provider,
+ FileDescriptor fd,
+ SelectableChannelCloser closer) {
+ super(provider);
+ this.fd = fd;
+ this.fdVal = IOUtil.fdVal(fd);
+ this.closer = closer;
+ }
+
+ @Override
+ public FileDescriptor getFD() {
+ return fd;
+ }
+
+ @Override
+ public int getFDVal() {
+ return fdVal;
+ }
+
+ @Override
+ public int validOps() {
+ return (SelectionKey.OP_READ | SelectionKey.OP_WRITE);
+ }
+
+ private boolean translateReadyOps(int ops,
+ int initialOps,
+ SelectionKeyImpl ski) {
+ int intOps = ski.nioInterestOps();
+ int oldOps = ski.nioReadyOps();
+ int newOps = initialOps;
+
+ if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) {
+ newOps = intOps;
+ ski.nioReadyOps(newOps);
+ return (newOps & ~oldOps) != 0;
+ }
+
+ if (((ops & Net.POLLIN) != 0) &&
+ ((intOps & SelectionKey.OP_READ) != 0))
+ newOps |= SelectionKey.OP_READ;
+
+ if (((ops & Net.POLLOUT) != 0) &&
+ ((intOps & SelectionKey.OP_WRITE) != 0))
+ newOps |= SelectionKey.OP_WRITE;
+
+ ski.nioReadyOps(newOps);
+ return (newOps & ~oldOps) != 0;
+ }
+
+ @Override
+ public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl ski) {
+ return translateReadyOps(ops, ski.nioReadyOps(), ski);
+ }
+
+ @Override
+ public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl ski) {
+ return translateReadyOps(ops, 0, ski);
+ }
+
+ @Override
+ public int translateInterestOps(int ops) {
+ int newOps = 0;
+ if ((ops & SelectionKey.OP_READ) != 0)
+ newOps |= Net.POLLIN;
+ if ((ops & SelectionKey.OP_WRITE) != 0)
+ newOps |= Net.POLLOUT;
+ return newOps;
+ }
+
+ @Override
+ protected void implConfigureBlocking(boolean block) throws IOException {
+ IOUtil.configureBlocking(fd, block);
+ }
+
+ @Override
+ protected void implCloseSelectableChannel() throws IOException {
+ closer.implCloseChannel(this);
+ }
+
+ @Override
+ public void kill() throws IOException {
+ closer.implReleaseChannel(this);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.net/share/classes/jdk/nio/package-info.java Sat Jun 23 09:34:09 2018 +0100
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Defines JDK-specific {@link java.nio.channels.Channel channel} APIs.
+ *
+ * @since 11
+ */
+
+package jdk.nio;
--- a/src/jdk.net/share/classes/module-info.java Sat Jun 23 08:03:52 2018 +0100
+++ b/src/jdk.net/share/classes/module-info.java Sat Jun 23 09:34:09 2018 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -31,5 +31,6 @@
*/
module jdk.net {
exports jdk.net;
+ exports jdk.nio;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/nio/Basic.java Sat Jun 23 09:34:09 2018 +0100
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8198372
+ * @modules jdk.net java.base/sun.nio.ch:+open
+ * @run testng Basic
+ * @summary Basic tests for jdk.nio.Channels
+ */
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+import jdk.nio.Channels;
+import jdk.nio.Channels.SelectableChannelCloser;
+
+import sun.nio.ch.IOUtil;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+@Test
+public class Basic {
+
+ /**
+ * A loopback connection
+ */
+ static class Connection implements Closeable {
+ private final SocketChannel sc1;
+ private final SocketChannel sc2;
+
+ private Connection(SocketChannel sc1, SocketChannel sc2) {
+ this.sc1 = sc1;
+ this.sc2 = sc2;
+ }
+
+ static Connection open() throws IOException {
+ try (ServerSocketChannel ssc = ServerSocketChannel.open()) {
+ InetAddress lb = InetAddress.getLoopbackAddress();
+ ssc.bind(new InetSocketAddress(lb, 0));
+ SocketChannel sc1 = SocketChannel.open(ssc.getLocalAddress());
+ SocketChannel sc2 = ssc.accept();
+ return new Connection(sc1, sc2);
+ }
+ }
+
+ SocketChannel channel1() {
+ return sc1;
+ }
+
+ SocketChannel channel2() {
+ return sc2;
+ }
+
+ public void close() throws IOException {
+ try {
+ sc1.close();
+ } finally {
+ sc2.close();
+ }
+ }
+ }
+
+ /**
+ * A SelectableChannelCloser that tracks if the implCloseChannel and
+ * implReleaseChannel methods are invoked
+ */
+ static class Closer implements SelectableChannelCloser {
+ int closeCount;
+ SelectableChannel invokedToClose;
+ int releaseCount;
+ SelectableChannel invokedToRelease;
+
+ @Override
+ public void implCloseChannel(SelectableChannel sc) {
+ closeCount++;
+ invokedToClose = sc;
+ }
+
+ @Override
+ public void implReleaseChannel(SelectableChannel sc) {
+ releaseCount++;
+ invokedToRelease = sc;
+ }
+ }
+
+ /**
+ * Basic test of channel registered with Selector
+ */
+ public void testSelect() throws IOException {
+ Selector sel = Selector.open();
+ try (Connection connection = Connection.open()) {
+
+ // create channel with the file descriptor from one end of the connection
+ FileDescriptor fd = getFD(connection.channel1());
+ SelectableChannel ch = Channels.readWriteSelectableChannel(fd, new Closer());
+
+ // register for read events, channel should not be selected
+ ch.configureBlocking(false);
+ SelectionKey key = ch.register(sel, SelectionKey.OP_READ);
+ int n = sel.selectNow();
+ assertTrue(n == 0);
+
+ // write bytes to other end of connection
+ SocketChannel peer = connection.channel2();
+ ByteBuffer msg = ByteBuffer.wrap("hello".getBytes("UTF-8"));
+ int nwrote = peer.write(msg);
+ assertTrue(nwrote >= 0);
+
+ // channel should be selected
+ n = sel.select();
+ assertTrue(n == 1);
+ assertTrue(sel.selectedKeys().contains(key));
+ assertTrue(key.isReadable());
+ assertFalse(key.isWritable());
+ sel.selectedKeys().clear();
+
+ // change interest set for writing, channel should be selected
+ key.interestOps(SelectionKey.OP_WRITE);
+ n = sel.select();
+ assertTrue(n == 1);
+ assertTrue(sel.selectedKeys().contains(key));
+ assertTrue(key.isWritable());
+ assertFalse(key.isReadable());
+ sel.selectedKeys().clear();
+
+ // change interest set for reading + writing, channel should be selected
+ key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
+ n = sel.select();
+ assertTrue(n == 1);
+ assertTrue(sel.selectedKeys().contains(key));
+ assertTrue(key.isWritable());
+ assertTrue(key.isReadable());
+ sel.selectedKeys().clear();
+
+ // change interest set to 0 to deregister, channel should not be selected
+ key.interestOps(0);
+ n = sel.selectNow();
+ assertTrue(n == 0);
+
+ } finally {
+ sel.close();
+ }
+ }
+
+ /**
+ * Test that the SelectableChannelCloser implCloseChannel method is invoked.
+ */
+ public void testImplCloseChannel() throws IOException {
+ try (Connection connection = Connection.open()) {
+ FileDescriptor fd = getFD(connection.channel1());
+ Closer closer = new Closer();
+ SelectableChannel ch = Channels.readWriteSelectableChannel(fd, closer);
+
+ // close channel twice, checking that the closer is invoked only once
+ for (int i=0; i<2; i++) {
+ ch.close();
+
+ // implCloseChannel should been invoked once
+ assertTrue(closer.closeCount == 1);
+ assertTrue(closer.invokedToClose == ch);
+
+ // implReleaseChannel should not have been invoked
+ assertTrue(closer.releaseCount == 0);
+ }
+ }
+ }
+
+ /**
+ * Test that the SelectableChannelCloser implReleaseChannel method is invoked.
+ */
+ public void testImplReleaseChannel() throws IOException {
+ Selector sel = Selector.open();
+ try (Connection connection = Connection.open()) {
+ FileDescriptor fd = getFD(connection.channel1());
+ Closer closer = new Closer();
+ SelectableChannel ch = Channels.readWriteSelectableChannel(fd, closer);
+
+ // register with Selector, invoking selectNow to ensure registered
+ ch.configureBlocking(false);
+ ch.register(sel, SelectionKey.OP_WRITE);
+ sel.selectNow();
+
+ // close channel
+ ch.close();
+
+ // implCloseChannel should have been invoked
+ assertTrue(closer.closeCount == 1);
+ assertTrue(closer.invokedToClose == ch);
+
+ // implReleaseChannel should not have been invoked
+ assertTrue(closer.releaseCount == 0);
+
+ // flush the selector
+ sel.selectNow();
+
+ // implReleaseChannel should have been invoked
+ assertTrue(closer.releaseCount == 1);
+ assertTrue(closer.invokedToRelease == ch);
+
+ } finally {
+ sel.close();
+ }
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidFileDescriptor() throws IOException {
+ FileDescriptor fd = IOUtil.newFD(-1);
+ Channels.readWriteSelectableChannel(fd, new SelectableChannelCloser() {
+ @Override
+ public void implCloseChannel(SelectableChannel sc) { }
+ @Override
+ public void implReleaseChannel(SelectableChannel sc) { }
+ });
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testNullFileDescriptor() throws IOException {
+ Channels.readWriteSelectableChannel(null, new SelectableChannelCloser() {
+ @Override
+ public void implCloseChannel(SelectableChannel sc) { }
+ @Override
+ public void implReleaseChannel(SelectableChannel sc) { }
+ });
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testNullCloser() throws IOException {
+ try (Connection connection = Connection.open()) {
+ FileDescriptor fd = getFD(connection.channel1());
+ Channels.readWriteSelectableChannel(fd, null);
+ }
+ }
+
+ private static FileDescriptor getFD(SocketChannel sc) {
+ try {
+ Class<?> clazz = sc.getClass();
+ Field f = clazz.getDeclaredField("fd");
+ f.setAccessible(true);
+ return (FileDescriptor) f.get(sc);
+ } catch (Exception e) {
+ throw new Error(e);
+ }
+ }
+}