changeset 53292:293ab6e11a1c fibers

ServerSocket.implAccept musy handle a mix of SocketImpl implementations
author alanb
date Sun, 13 Jan 2019 07:59:01 +0000
parents 5848a43df1cd
children d6496338e000
files src/java.base/share/classes/java/net/ServerSocket.java src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java
diffstat 2 files changed, 173 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/net/ServerSocket.java	Sun Jan 13 07:56:59 2019 +0000
+++ b/src/java.base/share/classes/java/net/ServerSocket.java	Sun Jan 13 07:59:01 2019 +0000
@@ -543,37 +543,78 @@
      * @spec JSR-51
      */
     protected final void implAccept(Socket s) throws IOException {
-        // create a new impl for the new connection
-        SocketImpl si = Socket.createImpl();
-        getImpl().accept(si);
+        SocketImpl impl = getImpl();
+        SocketImpl si = s.impl;
+
+        // Socket does not have a SocketImpl
+        if (si == null) {
+            // create a SocketImpl and accept the connection
+            si = Socket.createImpl();
+            impl.accept(si);
+            securityCheckAccept(si);
+
+            // a custom impl has accepted the connection with a NIO SocketImpl
+            if (!(impl instanceof NioSocketImpl) && (si instanceof NioSocketImpl)) {
+                ((NioSocketImpl) si).postCustomAccept();
+            }
+
+            // bind Socket to the SocketImpl and update socket state
+            s.setImpl(si);
+            s.postAccept();
+            return;
+        }
+
+        // ServerSocket or Socket is using NIO SocketImpl
+        if (impl instanceof NioSocketImpl || si instanceof NioSocketImpl) {
+            // not implemented yet
+            if (impl instanceof NioSocketImpl && impl.getClass() != NioSocketImpl.class)
+                throw new UnsupportedOperationException();
+            if (si instanceof NioSocketImpl && si.getClass() != NioSocketImpl.class)
+                throw new UnsupportedOperationException();
+
+            // accept connection via new SocketImpl
+            NioSocketImpl nsi = new NioSocketImpl(false);
+            impl.accept(nsi);
+            securityCheckAccept(nsi);
+
+            // copy state to the existing SocketImpl and update socket state
+            nsi.copyTo(si);
+            s.postAccept();
+            return;
+        }
+
+        // ServerSocket and Socket bound to custom SocketImpls
+        s.impl = null; // break connection to impl
+        boolean completed = false;
+        try {
+            si.reset();
+            si.fd = new FileDescriptor();
+            si.address = new InetAddress();
+            impl.accept(si);
+            securityCheckAccept(si);
+            completed = true;
+        } finally {
+            if (!completed)
+                si.reset();
+            s.impl = si;  // restore connection to impl
+        }
+        s.postAccept();
+    }
+
+    /**
+     * Invokes the security manager's checkAccept method. If the permission
+     * check fails then it closes the SocketImpl.
+     */
+    private void securityCheckAccept(SocketImpl si) throws IOException {
         SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
             try {
                 sm.checkAccept(si.getInetAddress().getHostAddress(), si.getPort());
-            } catch (SecurityException e) {
+            } catch (SecurityException se) {
                 si.close();
-                throw e;
+                throw se;
             }
         }
-
-        // copy the timeout from the old impl to the new impl
-        SocketImpl previous = s.impl;
-        if (previous != null) {
-            int timeout = (int) previous.getOption(SocketOptions.SO_TIMEOUT);
-            if (timeout != 0) {
-                si.setOption(SocketOptions.SO_TIMEOUT, timeout);
-            }
-        }
-
-        // replace the impl, eagerly closing the old impl to ensure any
-        // resources can be released early
-        s.setImpl(si);
-        s.postAccept();
-        if (previous != null) {
-            try {
-                previous.close();
-            } catch (IOException ignore) { }
-        }
     }
 
     /**
--- a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java	Sun Jan 13 07:56:59 2019 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java	Sun Jan 13 07:59:01 2019 +0000
@@ -379,6 +379,74 @@
     }
 
     /**
+     * For use by ServerSocket to set the state and other fields after a
+     * connection is accepted by a ServerSocket using a custom SocketImpl.
+     * The protected fields defined by SocketImpl should be set.
+     */
+    public void postCustomAccept() {
+        synchronized (stateLock) {
+            assert state == ST_NEW;
+            assert fd.valid() && localport != 0 && address != null && port != 0;
+            stream = true;
+            closer = FileDescriptorCloser.create(this);
+            state = ST_CONNECTED;
+        }
+    }
+
+    /**
+     * For use by ServerSocket to copy the state from this connected SocketImpl
+     * to a target SocketImpl. If the target SocketImpl is not a newly created
+     * SocketImpl then it is first closed to release any resources. The target
+     * SocketImpl becomes the owner of the file descriptor, this SocketImpl
+     * is marked as closed and should be discarded.
+     */
+    public void copyTo(SocketImpl si) {
+        if (si instanceof NioSocketImpl) {
+            NioSocketImpl nsi = (NioSocketImpl) si;
+            synchronized (nsi.stateLock) {
+                if (nsi.state != ST_NEW) {
+                    try {
+                        nsi.close();
+                    } catch (IOException ignore) { }
+                }
+                synchronized (this.stateLock) {
+                    // this SocketImpl should be connected
+                    assert state == ST_CONNECTED && fd.valid()
+                        && localport != 0 && address != null && port != 0;
+
+                    // copy fields
+                    nsi.fd = this.fd;
+                    nsi.stream = this.stream;
+                    nsi.closer = FileDescriptorCloser.create(nsi);
+                    nsi.localport = this.localport;
+                    nsi.address = this.address;
+                    nsi.port = this.port;
+                    nsi.state = ST_CONNECTED;
+
+                    // disable closer to prevent GC'ing of this impl from
+                    // closing the file descriptor
+                    this.closer.disable();
+                    this.state = ST_CLOSED;
+                }
+            }
+        } else {
+            synchronized (this.stateLock) {
+                // this SocketImpl should be connected
+                assert state == ST_CONNECTED && fd.valid()
+                        && localport != 0 && address != null && port != 0;
+
+                // set fields in foreign impl
+                setSocketImplFields(si, fd, localport, address, port);
+
+                // disable closer to prevent GC'ing of this impl from
+                // closing the file descriptor
+                this.closer.disable();
+                this.state = ST_CLOSED;
+            }
+        }
+    }
+
+    /**
      * Marks the beginning of a connect operation that might block.
      * @throws SocketException if the socket is closed or already connected
      */
@@ -636,30 +704,14 @@
                 nsi.state = ST_CONNECTED;
             }
         } else {
-            // foreign SocketImpl, patch the known fields
-            PrivilegedExceptionAction<Void> pa = () -> {
-                setSocketImplField(si, "fd", newfd);
-                setSocketImplField(si, "localport", localAddress.getPort());
-                setSocketImplField(si, "address", remoteAddress.getAddress());
-                setSocketImplField(si, "port", remoteAddress.getPort());
-                return null;
-            };
-            try {
-                AccessController.doPrivileged(pa);
-            } catch (PrivilegedActionException pae) {
-                throw new InternalError(pae);
-            }
+            // set fields in foreign impl
+            setSocketImplFields(si, newfd,
+                                localAddress.getPort(),
+                                remoteAddress.getAddress(),
+                                remoteAddress.getPort());
         }
     }
 
-    private void setSocketImplField(SocketImpl si, String name, Object value)
-        throws Exception
-    {
-        Field field = SocketImpl.class.getDeclaredField(name);
-        field.setAccessible(true);
-        field.set(si, value);
-    }
-
     @Override
     protected InputStream getInputStream() {
         return new InputStream() {
@@ -1097,6 +1149,10 @@
                 }
             }
         }
+
+        boolean disable() {
+            return CLOSED.compareAndSet(this, false, true);
+        }
     }
 
     /**
@@ -1126,4 +1182,35 @@
         assert fdVal == IOUtil.fdVal(fd);
         return fdVal;
     }
+
+    /**
+     * Sets the SocketImpl fields to the given values.
+     */
+    private static void setSocketImplFields(SocketImpl si,
+                                            FileDescriptor fd,
+                                            int localport,
+                                            InetAddress address,
+                                            int port)
+    {
+        PrivilegedExceptionAction<Void> pa = () -> {
+            setSocketImplField(si, "fd", fd);
+            setSocketImplField(si, "localport", localport);
+            setSocketImplField(si, "address", address);
+            setSocketImplField(si, "port", port);
+            return null;
+        };
+        try {
+            AccessController.doPrivileged(pa);
+        } catch (PrivilegedActionException pae) {
+            throw new InternalError(pae);
+        }
+    }
+
+    private static void setSocketImplField(SocketImpl si, String name, Object value)
+        throws Exception
+    {
+        Field field = SocketImpl.class.getDeclaredField(name);
+        field.setAccessible(true);
+        field.set(si, value);
+    }
 }