# HG changeset patch # User alanb # Date 1550254037 0 # Node ID 997178749c87574d1749e0fa2da7656b940e1d14 # Parent e0e1493fa16609f709cb397a13c337431c1c2623 Throw SocketException consistently after connection reset detected diff -r e0e1493fa166 -r 997178749c87 src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java --- a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java Fri Feb 15 10:39:45 2019 +0000 +++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java Fri Feb 15 18:07:17 2019 +0000 @@ -56,6 +56,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.ref.CleanerFactory; +import sun.net.ConnectionResetException; import sun.net.NetHooks; import sun.net.PlatformSocketImpl; import sun.net.ResourceManager; @@ -75,14 +76,10 @@ * blocking. If a connect, accept or read is attempted with a timeout then the * socket is changed to non-blocking mode. When in non-blocking mode, operations * that don't complete immediately will poll the socket. - * - * Behavior differences to examine: - * "Connection reset" handling differs to PlainSocketImpl for cases where - * an application continues to call read or available after a reset. */ public final class NioSocketImpl extends SocketImpl implements PlatformSocketImpl { - private static final NativeDispatcher nd = new SocketDispatcher(); + private static final NativeDispatcher nd = new SocketDispatcher(true); // The maximum number of bytes to read/write per syscall to avoid needing // a huge buffer from the temporary buffer cache @@ -128,6 +125,20 @@ private volatile boolean isInputClosed; private volatile boolean isOutputClosed; + // socket input/output streams + private volatile InputStream in; + private volatile OutputStream out; + private static final VarHandle IN, OUT; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + IN = l.findVarHandle(NioSocketImpl.class, "in", InputStream.class); + OUT = l.findVarHandle(NioSocketImpl.class, "out", OutputStream.class); + } catch (Exception e) { + throw new InternalError(e); + } + } + /** * Creates a instance of this SocketImpl. * @param server true if this is a SocketImpl for a ServerSocket @@ -741,78 +752,110 @@ @Override protected InputStream getInputStream() { - return new InputStream() { - private volatile boolean eof; // to emulate legacy SocketInputStream - @Override - public int read() throws IOException { - byte[] a = new byte[1]; - int n = read(a, 0, 1); - return (n > 0) ? (a[0] & 0xff) : -1; + InputStream in = this.in; + if (in == null) { + in = new SocketInputStream(this); + if (!IN.compareAndSet(this, null, in)) { + in = this.in; } - @Override - public int read(byte[] b, int off, int len) throws IOException { - Objects.checkFromIndexSize(off, len, b.length); - if (eof) { - return -1; // return -1, even if socket is closed - } else if (len == 0) { - return 0; // return 0, even if socket is closed - } else { - try { - // read up to MAX_BUFFER_SIZE bytes - int size = Math.min(len, MAX_BUFFER_SIZE); - int n = NioSocketImpl.this.read(b, off, size); - if (n == -1) - eof = true; - return n; - } catch (SocketTimeoutException e) { - throw e; - } catch (IOException ioe) { - throw new SocketException(ioe.getMessage()); - } + } + return in; + } + + private static class SocketInputStream extends InputStream { + private final NioSocketImpl impl; + // EOF or connection reset detected, not thread safe + private boolean eof, reset; + SocketInputStream(NioSocketImpl impl) { + this.impl = impl; + } + @Override + public int read() throws IOException { + byte[] a = new byte[1]; + int n = read(a, 0, 1); + return (n > 0) ? (a[0] & 0xff) : -1; + } + @Override + public int read(byte[] b, int off, int len) throws IOException { + Objects.checkFromIndexSize(off, len, b.length); + if (eof) { + return -1; + } else if (reset) { + throw new SocketException("Connection reset"); + } else if (len == 0) { + return 0; + } else { + try { + // read up to MAX_BUFFER_SIZE bytes + int size = Math.min(len, MAX_BUFFER_SIZE); + int n = impl.read(b, off, size); + if (n == -1) + eof = true; + return n; + } catch (ConnectionResetException e) { + reset = true; + throw new SocketException("Connection reset"); + } catch (SocketTimeoutException e) { + throw e; + } catch (IOException ioe) { + throw new SocketException(ioe.getMessage()); } } - @Override - public int available() throws IOException { - return NioSocketImpl.this.available(); - } - @Override - public void close() throws IOException { - NioSocketImpl.this.close(); - } - }; + } + @Override + public int available() throws IOException { + return impl.available(); + } + @Override + public void close() throws IOException { + impl.close(); + } } @Override protected OutputStream getOutputStream() { - return new OutputStream() { - @Override - public void write(int b) throws IOException { - byte[] a = new byte[] { (byte) b }; - write(a, 0, 1); + OutputStream out = this.out; + if (out == null) { + out = new SocketOutputStream(this); + if (!OUT.compareAndSet(this, null, out)) { + out = this.out; } - @Override - public void write(byte[] b, int off, int len) throws IOException { - Objects.checkFromIndexSize(off, len, b.length); - if (len > 0) { - try { - int pos = off; - int end = off + len; - while (pos < end) { - // write up to MAX_BUFFER_SIZE bytes - int size = Math.min((end - pos), MAX_BUFFER_SIZE); - int n = NioSocketImpl.this.write(b, pos, size); - pos += n; - } - } catch (IOException ioe) { - throw new SocketException(ioe.getMessage()); + } + return out; + } + + private static class SocketOutputStream extends OutputStream { + private final NioSocketImpl impl; + SocketOutputStream(NioSocketImpl impl) { + this.impl = impl; + } + @Override + public void write(int b) throws IOException { + byte[] a = new byte[]{(byte) b}; + write(a, 0, 1); + } + @Override + public void write(byte[] b, int off, int len) throws IOException { + Objects.checkFromIndexSize(off, len, b.length); + if (len > 0) { + try { + int pos = off; + int end = off + len; + while (pos < end) { + // write up to MAX_BUFFER_SIZE bytes + int size = Math.min((end - pos), MAX_BUFFER_SIZE); + int n = impl.write(b, pos, size); + pos += n; } + } catch (IOException ioe) { + throw new SocketException(ioe.getMessage()); } } - @Override - public void close() throws IOException { - NioSocketImpl.this.close(); - } - }; + } + @Override + public void close() throws IOException { + impl.close(); + } } @Override diff -r e0e1493fa166 -r 997178749c87 src/java.base/unix/classes/sun/nio/ch/SocketDispatcher.java --- a/src/java.base/unix/classes/sun/nio/ch/SocketDispatcher.java Fri Feb 15 10:39:45 2019 +0000 +++ b/src/java.base/unix/classes/sun/nio/ch/SocketDispatcher.java Fri Feb 15 18:07:17 2019 +0000 @@ -34,9 +34,22 @@ */ class SocketDispatcher extends NativeDispatcher { + private final boolean detectConnectionReset; + + SocketDispatcher(boolean detectConnectionReset) { + this.detectConnectionReset = detectConnectionReset; + } + + SocketDispatcher() { + this(false); + } int read(FileDescriptor fd, long address, int len) throws IOException { - return FileDispatcherImpl.read0(fd, address, len); + if (detectConnectionReset) { + return read0(fd, address, len); + } else { + return FileDispatcherImpl.read0(fd, address, len); + } } long readv(FileDescriptor fd, long address, int len) throws IOException { @@ -58,4 +71,20 @@ void preClose(FileDescriptor fd) throws IOException { FileDispatcherImpl.preClose0(fd); } + + // -- Native methods -- + + /** + * Reads up to len bytes from a socket with special handling for "connection + * reset". + * + * @throws sun.net.ConnectionResetException if connection reset is detected + * @throws IOException if another I/O error occurs + */ + private static native int read0(FileDescriptor fd, long address, int len) + throws IOException; + + static { + IOUtil.load(); + } } diff -r e0e1493fa166 -r 997178749c87 src/java.base/unix/native/libnio/ch/SocketDispatcher.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/unix/native/libnio/ch/SocketDispatcher.c Fri Feb 15 18:07:17 2019 +0000 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019, 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. + */ + + #include + #include + + #include "jni.h" + #include "jni_util.h" + #include "jlong.h" + #include "nio.h" + #include "nio_util.h" + #include "sun_nio_ch_SocketDispatcher.h" + + JNIEXPORT jint JNICALL + Java_sun_nio_ch_SocketDispatcher_read0(JNIEnv *env, jclass clazz, + jobject fdo, jlong address, jint len) + { + jint fd = fdval(env, fdo); + void *buf = (void *)jlong_to_ptr(address); + jint n = read(fd, buf, len); + if ((n == -1) && (errno == ECONNRESET || errno == EPIPE)) { + JNU_ThrowByName(env, "sun/net/ConnectionResetException", "Connection reset"); + return IOS_THROWN; + } else { + return convertReturnVal(env, n, JNI_TRUE); + } + } diff -r e0e1493fa166 -r 997178749c87 src/java.base/windows/classes/sun/nio/ch/SocketDispatcher.java --- a/src/java.base/windows/classes/sun/nio/ch/SocketDispatcher.java Fri Feb 15 10:39:45 2019 +0000 +++ b/src/java.base/windows/classes/sun/nio/ch/SocketDispatcher.java Fri Feb 15 18:07:17 2019 +0000 @@ -32,12 +32,11 @@ * for read and write operations. */ -class SocketDispatcher extends NativeDispatcher -{ +class SocketDispatcher extends NativeDispatcher { - static { - IOUtil.load(); - } + SocketDispatcher() { } + + SocketDispatcher(boolean ignore) { } int read(FileDescriptor fd, long address, int len) throws IOException { return read0(fd, address, len); @@ -63,7 +62,8 @@ close0(fd); } - //-- Native methods + // -- Native methods -- + static native int read0(FileDescriptor fd, long address, int len) throws IOException; @@ -79,4 +79,8 @@ static native void preClose0(FileDescriptor fd) throws IOException; static native void close0(FileDescriptor fd) throws IOException; + + static { + IOUtil.load(); + } }