8221252: (sc) SocketChannel and its socket adaptor need to handle connection reset

Reviewed-by: bpb
This commit is contained in:
Alan Bateman 2019-03-22 11:35:35 +00:00
parent cc590f5765
commit 3a4d5db248
13 changed files with 507 additions and 88 deletions
src
java.base
jdk.sctp/unix/classes/sun/nio/ch/sctp
test/jdk/java/nio/channels/SocketChannel

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 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
@ -81,4 +81,12 @@ public final class IOStatus {
return ((n > EOF) || (n < UNSUPPORTED_CASE));
}
/**
* Returns true if the error code is UNAVAILABLE or INTERRUPTED, the
* error codes to indicate that an I/O operation can be retried.
*/
static boolean okayToRetry(long n) {
return (n == IOStatus.UNAVAILABLE) || (n == IOStatus.INTERRUPTED);
}
}

@ -310,6 +310,12 @@ public class Net {
static final ExtendedSocketOptions extendedOptions =
ExtendedSocketOptions.getInstance();
static void setSocketOption(FileDescriptor fd, SocketOption<?> name, Object value)
throws IOException
{
setSocketOption(fd, Net.UNSPEC, name, value);
}
static void setSocketOption(FileDescriptor fd, ProtocolFamily family,
SocketOption<?> name, Object value)
throws IOException
@ -372,8 +378,13 @@ public class Net {
setIntOption0(fd, mayNeedConversion, key.level(), key.name(), arg, isIPv6);
}
static Object getSocketOption(FileDescriptor fd, ProtocolFamily family,
SocketOption<?> name)
static Object getSocketOption(FileDescriptor fd, SocketOption<?> name)
throws IOException
{
return getSocketOption(fd, Net.UNSPEC, name);
}
static Object getSocketOption(FileDescriptor fd, ProtocolFamily family, SocketOption<?> name)
throws IOException
{
Class<?> type = name.type();
@ -426,8 +437,7 @@ public class Net {
return socket(UNSPEC, stream);
}
static FileDescriptor socket(ProtocolFamily family, boolean stream)
throws IOException {
static FileDescriptor socket(ProtocolFamily family, boolean stream) throws IOException {
boolean preferIPv6 = isIPv6Available() &&
(family != StandardProtocolFamily.INET);
return IOUtil.newFD(socket0(preferIPv6, stream, false, fastLoopback));
@ -525,20 +535,43 @@ public class Net {
int level, int opt, int arg, boolean isIPv6)
throws IOException;
/**
* Polls a file descriptor for events.
* @param timeout the timeout to wait; 0 to not wait, -1 to wait indefinitely
* @return the polled events or 0 if no events are polled
*/
static native int poll(FileDescriptor fd, int events, long timeout)
throws IOException;
/**
* Performs a non-blocking poll of a file descriptor.
* @return the polled events or 0 if no events are polled
*/
static int pollNow(FileDescriptor fd, int events) throws IOException {
return poll(fd, events, 0);
}
/**
* Polls a connecting socket to test if the connection has been established.
*
* @apiNote This method is public to allow it be used by code in jdk.sctp.
*
* @param timeout the timeout to wait; 0 to not wait, -1 to wait indefinitely
* @return 1 if connected, 0 if not connected, or IOS_INTERRUPTED
* @return true if connected
*/
public static native int pollConnect(FileDescriptor fd, long timeout)
public static native boolean pollConnect(FileDescriptor fd, long timeout)
throws IOException;
/**
* Performs a non-blocking poll of a connecting socket to test if the
* connection has been established.
*
* @return true if connected
*/
static boolean pollConnectNow(FileDescriptor fd) throws IOException {
return pollConnect(fd, 0);
}
/**
* Return the number of bytes in the socket input buffer.
*/

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 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
@ -216,6 +216,11 @@ class SocketAdaptor
}
}
}
@Override
public int available() throws IOException {
return sc.available();
}
}
private InputStream socketInputStream = null;

@ -32,6 +32,7 @@ import java.net.InetSocketAddress;
import java.net.ProtocolFamily;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketOption;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
@ -52,6 +53,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import sun.net.ConnectionResetException;
import sun.net.NetHooks;
import sun.net.ext.ExtendedSocketOptions;
import sun.net.util.SocketExceptions;
@ -85,6 +87,9 @@ class SocketChannelImpl
private volatile boolean isInputClosed;
private volatile boolean isOutputClosed;
// Connection reset protected by readLock
private boolean connectionReset;
// -- The following fields are protected by stateLock
// set true when exclusive binding is on and SO_REUSEADDR is emulated
@ -230,7 +235,7 @@ class SocketChannelImpl
}
// no options that require special handling
Net.setSocketOption(fd, Net.UNSPEC, name, value);
Net.setSocketOption(fd, name, value);
return this;
}
}
@ -260,7 +265,7 @@ class SocketChannelImpl
}
// no options that require special handling
return (T) Net.getSocketOption(fd, Net.UNSPEC, name);
return (T) Net.getSocketOption(fd, name);
}
}
@ -334,6 +339,10 @@ class SocketChannelImpl
}
}
private void throwConnectionReset() throws SocketException {
throw new SocketException("Connection reset");
}
@Override
public int read(ByteBuffer buf) throws IOException {
Objects.requireNonNull(buf);
@ -345,6 +354,10 @@ class SocketChannelImpl
try {
beginRead(blocking);
// check if connection has been reset
if (connectionReset)
throwConnectionReset();
// check if input is shutdown
if (isInputClosed)
return IOStatus.EOF;
@ -356,6 +369,9 @@ class SocketChannelImpl
} else {
n = IOUtil.read(fd, buf, -1, nd);
}
} catch (ConnectionResetException e) {
connectionReset = true;
throwConnectionReset();
} finally {
endRead(blocking, n > 0);
if (n <= 0 && isInputClosed)
@ -380,6 +396,10 @@ class SocketChannelImpl
try {
beginRead(blocking);
// check if connection has been reset
if (connectionReset)
throwConnectionReset();
// check if input is shutdown
if (isInputClosed)
return IOStatus.EOF;
@ -391,6 +411,9 @@ class SocketChannelImpl
} else {
n = IOUtil.read(fd, dsts, offset, length, nd);
}
} catch (ConnectionResetException e) {
connectionReset = true;
throwConnectionReset();
} finally {
endRead(blocking, n > 0);
if (n <= 0 && isInputClosed)
@ -769,15 +792,13 @@ class SocketChannelImpl
boolean connected = false;
try {
beginFinishConnect(blocking);
int n = 0;
if (blocking) {
do {
n = Net.pollConnect(fd, -1);
} while ((n == 0 || n == IOStatus.INTERRUPTED) && isOpen());
connected = Net.pollConnect(fd, -1);
} while (!connected && isOpen());
} else {
n = Net.pollConnect(fd, 0);
connected = Net.pollConnect(fd, 0);
}
connected = (n > 0);
} finally {
endFinishConnect(blocking, connected);
}
@ -1006,6 +1027,20 @@ class SocketChannelImpl
}
}
/**
* Return the number of bytes in the socket input buffer.
*/
int available() throws IOException {
synchronized (stateLock) {
ensureOpenAndConnected();
if (isInputClosed) {
return 0;
} else {
return Net.available(fd);
}
}
}
/**
* Translates native poll revent ops into a ready operation ops
*/

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 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
@ -34,13 +34,28 @@ import java.io.IOException;
*/
class SocketDispatcher extends NativeDispatcher {
SocketDispatcher() { }
/**
* 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
*/
int read(FileDescriptor fd, long address, int len) throws IOException {
return FileDispatcherImpl.read0(fd, address, len);
return read0(fd, address, len);
}
/**
* Scattering read from a socket into len buffers with special handling for
* "connection reset".
*
* @throws sun.net.ConnectionResetException if connection reset is detected
* @throws IOException if another I/O error occurs
*/
long readv(FileDescriptor fd, long address, int len) throws IOException {
return FileDispatcherImpl.readv0(fd, address, len);
return readv0(fd, address, len);
}
int write(FileDescriptor fd, long address, int len) throws IOException {
@ -58,4 +73,16 @@ class SocketDispatcher extends NativeDispatcher {
void preClose(FileDescriptor fd) throws IOException {
FileDispatcherImpl.preClose0(fd);
}
// -- Native methods --
private static native int read0(FileDescriptor fd, long address, int len)
throws IOException;
private static native long readv0(FileDescriptor fd, long address, int len)
throws IOException;
static {
IOUtil.load();
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 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
@ -31,6 +31,8 @@ import java.net.*;
import java.util.concurrent.*;
import java.io.IOException;
import java.io.FileDescriptor;
import sun.net.ConnectionResetException;
import sun.net.NetHooks;
import sun.net.util.SocketExceptions;
import sun.security.action.GetPropertyAction;
@ -415,6 +417,8 @@ class UnixAsynchronousSocketChannelImpl
enableReading();
if (x instanceof ClosedChannelException)
x = new AsynchronousCloseException();
if (x instanceof ConnectionResetException)
x = new IOException(x.getMessage());
exc = x;
} finally {
// restart poll in case of concurrent write
@ -546,6 +550,8 @@ class UnixAsynchronousSocketChannelImpl
} catch (Throwable x) {
if (x instanceof ClosedChannelException)
x = new AsynchronousCloseException();
if (x instanceof ConnectionResetException)
x = new IOException(x.getMessage());
exc = x;
} finally {
if (!pending)

@ -804,7 +804,7 @@ Java_sun_nio_ch_Net_poll(JNIEnv* env, jclass this, jobject fdo, jint events, jlo
}
}
JNIEXPORT jint JNICALL
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_pollConnect(JNIEnv *env, jobject this, jobject fdo, jlong timeout)
{
jint fd = fdval(env, fdo);
@ -828,23 +828,22 @@ Java_sun_nio_ch_Net_pollConnect(JNIEnv *env, jobject this, jobject fdo, jlong ti
errno = 0;
result = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &n);
if (result < 0) {
return handleSocketError(env, errno);
handleSocketError(env, errno);
return JNI_FALSE;
} else if (error) {
return handleSocketError(env, error);
handleSocketError(env, error);
return JNI_FALSE;
} else if ((poller.revents & POLLHUP) != 0) {
return handleSocketError(env, ENOTCONN);
handleSocketError(env, ENOTCONN);
return JNI_FALSE;
}
// connected
return 1;
} else if (result == 0) {
return 0;
return JNI_TRUE;
} else if (result == 0 || errno == EINTR) {
return JNI_FALSE;
} else {
if (errno == EINTR) {
return IOS_INTERRUPTED;
} else {
JNU_ThrowIOExceptionWithLastError(env, "poll failed");
return IOS_THROWN;
}
JNU_ThrowIOExceptionWithLastError(env, "poll failed");
return JNI_FALSE;
}
}

@ -0,0 +1,65 @@
/*
* 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 <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#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);
}
}
JNIEXPORT jlong JNICALL
Java_sun_nio_ch_SocketDispatcher_readv0(JNIEnv *env, jclass clazz,
jobject fdo, jlong address, jint len)
{
jint fd = fdval(env, fdo);
struct iovec *iov = (struct iovec *)jlong_to_ptr(address);
jlong n = readv(fd, iov, len);
if ((n == -1) && (errno == ECONNRESET || errno == EPIPE)) {
JNU_ThrowByName(env, "sun/net/ConnectionResetException", "Connection reset");
return IOS_THROWN;
} else {
return convertLongReturnVal(env, n, JNI_TRUE);
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 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
@ -25,19 +25,16 @@
package sun.nio.ch;
import java.io.*;
import java.io.FileDescriptor;
import java.io.IOException;
/**
* Allows different platforms to call different native methods
* for read and write operations.
*/
class SocketDispatcher extends NativeDispatcher
{
static {
IOUtil.load();
}
class SocketDispatcher extends NativeDispatcher {
SocketDispatcher() { }
int read(FileDescriptor fd, long address, int len) throws IOException {
return read0(fd, address, len);
@ -63,7 +60,8 @@ class SocketDispatcher extends NativeDispatcher
close0(fd);
}
//-- Native methods
// -- Native methods --
static native int read0(FileDescriptor fd, long address, int len)
throws IOException;
@ -79,4 +77,8 @@ class SocketDispatcher extends NativeDispatcher
static native void preClose0(FileDescriptor fd) throws IOException;
static native void close0(FileDescriptor fd) throws IOException;
static {
IOUtil.load();
}
}

@ -660,7 +660,7 @@ Java_sun_nio_ch_Net_poll(JNIEnv* env, jclass this, jobject fdo, jint events, jlo
return rv;
}
JNIEXPORT jint JNICALL
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_pollConnect(JNIEnv* env, jclass this, jobject fdo, jlong timeout)
{
int optError = 0;
@ -684,13 +684,13 @@ Java_sun_nio_ch_Net_pollConnect(JNIEnv* env, jclass this, jobject fdo, jlong tim
if (result == SOCKET_ERROR) {
handleSocketError(env, WSAGetLastError());
return IOS_THROWN;
return JNI_FALSE;
} else if (result == 0) {
return 0;
return JNI_FALSE;
} else {
// connection established if writable and no error to check
if (FD_ISSET(fd, &wr) && !FD_ISSET(fd, &ex)) {
return 1;
return JNI_TRUE;
}
result = getsockopt((SOCKET)fd,
SOL_SOCKET,
@ -699,17 +699,13 @@ Java_sun_nio_ch_Net_pollConnect(JNIEnv* env, jclass this, jobject fdo, jlong tim
&n);
if (result == SOCKET_ERROR) {
int lastError = WSAGetLastError();
if (lastError == WSAEINPROGRESS) {
return IOS_UNAVAILABLE;
if (lastError != WSAEINPROGRESS) {
NET_ThrowNew(env, lastError, "getsockopt");
}
NET_ThrowNew(env, lastError, "getsockopt");
return IOS_THROWN;
}
if (optError != NO_ERROR) {
} else if (optError != NO_ERROR) {
handleSocketError(env, optError);
return IOS_THROWN;
}
return 0;
return JNI_FALSE;
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 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
@ -72,7 +72,11 @@ Java_sun_nio_ch_SocketDispatcher_read0(JNIEnv *env, jclass clazz, jobject fdo,
if (theErr == WSAEWOULDBLOCK) {
return IOS_UNAVAILABLE;
}
JNU_ThrowIOExceptionWithLastError(env, "Read failed");
if (theErr == WSAECONNRESET) {
JNU_ThrowByName(env, "sun/net/ConnectionResetException", "Connection reset");
} else {
JNU_ThrowIOExceptionWithLastError(env, "Read failed");
}
return IOS_THROWN;
}
@ -128,7 +132,11 @@ Java_sun_nio_ch_SocketDispatcher_readv0(JNIEnv *env, jclass clazz, jobject fdo,
if (theErr == WSAEWOULDBLOCK) {
return IOS_UNAVAILABLE;
}
JNU_ThrowIOExceptionWithLastError(env, "Vector read failed");
if (theErr == WSAECONNRESET) {
JNU_ThrowByName(env, "sun/net/ConnectionResetException", "Connection reset");
} else {
JNU_ThrowIOExceptionWithLastError(env, "Vector read failed");
}
return IOS_THROWN;
}
@ -174,7 +182,11 @@ Java_sun_nio_ch_SocketDispatcher_write0(JNIEnv *env, jclass clazz, jobject fdo,
if (theErr == WSAEWOULDBLOCK) {
return IOS_UNAVAILABLE;
}
JNU_ThrowIOExceptionWithLastError(env, "Write failed");
if (theErr == WSAECONNRESET) {
JNU_ThrowIOException(env, "Connection reset by peer");
} else {
JNU_ThrowIOExceptionWithLastError(env, "Write failed");
}
return IOS_THROWN;
}
}
@ -256,7 +268,11 @@ Java_sun_nio_ch_SocketDispatcher_writev0(JNIEnv *env, jclass clazz,
if (theErr == WSAEWOULDBLOCK) {
return IOS_UNAVAILABLE;
}
JNU_ThrowIOExceptionWithLastError(env, "Vector write failed");
if (theErr == WSAECONNRESET) {
JNU_ThrowIOException(env, "Connection reset by peer");
} else {
JNU_ThrowIOExceptionWithLastError(env, "Vector write failed");
}
return IOS_THROWN;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 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
@ -465,7 +465,7 @@ public class SctpChannelImpl extends SctpChannel
if (state != ChannelState.PENDING)
throw new NoConnectionPendingException();
}
int n = 0;
boolean connected = false;
try {
try {
begin();
@ -477,26 +477,11 @@ public class SctpChannelImpl extends SctpChannel
receiverThread = NativeThread.current();
}
if (!isBlocking()) {
for (;;) {
n = Net.pollConnect(fd, 0);
if ( (n == IOStatus.INTERRUPTED)
&& isOpen())
continue;
break;
}
connected = Net.pollConnect(fd, 0);
} else {
for (;;) {
n = Net.pollConnect(fd, -1);
if (n == 0) {
// Loop in case of
// spurious notifications
continue;
}
if ( (n == IOStatus.INTERRUPTED)
&& isOpen())
continue;
break;
}
do {
connected = Net.pollConnect(fd, -1);
} while (!connected && isOpen());
}
}
} finally {
@ -504,16 +489,10 @@ public class SctpChannelImpl extends SctpChannel
receiverThread = 0;
if (state == ChannelState.KILLPENDING) {
kill();
/* poll()/getsockopt() does not report
* error (throws exception, with n = 0)
* on Linux platform after dup2 and
* signal-wakeup. Force n to 0 so the
* end() can throw appropriate exception */
n = 0;
connected = false;
}
}
end((n > 0) || (n == IOStatus.UNAVAILABLE));
assert IOStatus.check(n);
end(connected);
}
} catch (IOException x) {
/* If an exception was thrown, close the channel after
@ -523,7 +502,7 @@ public class SctpChannelImpl extends SctpChannel
throw x;
}
if (n > 0) {
if (connected) {
synchronized (stateLock) {
state = ChannelState.CONNECTED;
if (!isBound()) {

@ -0,0 +1,248 @@
/*
* 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.
*
* 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
* @requires os.family != "solaris"
* @run testng ConnectionReset
* @summary Test behavior of SocketChannel.read and the Socket adaptor read
* and available methods when a connection is reset
*/
import java.io.InputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.lang.reflect.Method;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@Test
public class ConnectionReset {
static final int REPEAT_COUNT = 5;
/**
* Tests SocketChannel.read when the connection is reset and there are no
* bytes to read.
*/
public void testSocketChannelReadNoData() throws IOException {
System.out.println("testSocketChannelReadNoData");
withResetConnection(null, sc -> {
ByteBuffer bb = ByteBuffer.allocate(100);
for (int i=0; i<REPEAT_COUNT; i++) {
try {
sc.read(bb);
assertTrue(false);
} catch (IOException ioe) {
System.out.format("read => %s (expected)%n", ioe);
}
}
});
}
/**
* Tests SocketChannel.read when the connection is reset and there are bytes
* to read.
*/
public void testSocketChannelReadData() throws IOException {
System.out.println("testSocketChannelReadData");
byte[] data = { 1, 2, 3 };
withResetConnection(data, sc -> {
int remaining = data.length;
ByteBuffer bb = ByteBuffer.allocate(remaining + 100);
for (int i=0; i<REPEAT_COUNT; i++) {
try {
int bytesRead = sc.read(bb);
if (bytesRead == -1) {
System.out.println("read => EOF");
} else {
System.out.println("read => " + bytesRead + " byte(s)");
}
assertTrue(bytesRead > 0);
remaining -= bytesRead;
assertTrue(remaining >= 0);
} catch (IOException ioe) {
System.out.format("read => %s%n", ioe);
remaining = 0;
}
}
});
}
/**
* Tests available before Socket read when the connection is reset and there
* are no bytes to read.
*/
public void testAvailableBeforeSocketReadNoData() throws IOException {
System.out.println("testAvailableBeforeSocketReadNoData");
withResetConnection(null, sc -> {
Socket s = sc.socket();
InputStream in = s.getInputStream();
for (int i=0; i<REPEAT_COUNT; i++) {
int bytesAvailable = in.available();
System.out.format("available => %d%n", bytesAvailable);
assertTrue(bytesAvailable == 0);
try {
int bytesRead = in.read();
if (bytesRead == -1) {
System.out.println("read => EOF");
} else {
System.out.println("read => 1 byte");
}
assertTrue(false);
} catch (IOException ioe) {
System.out.format("read => %s (expected)%n", ioe);
}
}
});
}
/**
* Tests available before Socket read when the connection is reset and there
* are bytes to read.
*/
public void testAvailableBeforeSocketReadData() throws IOException {
System.out.println("testAvailableBeforeSocketReadData");
byte[] data = { 1, 2, 3 };
withResetConnection(data, sc -> {
Socket s = sc.socket();
InputStream in = s.getInputStream();
int remaining = data.length;
for (int i=0; i<REPEAT_COUNT; i++) {
int bytesAvailable = in.available();
System.out.format("available => %d%n", bytesAvailable);
assertTrue(bytesAvailable <= remaining);
try {
int bytesRead = in.read();
if (bytesRead == -1) {
System.out.println("read => EOF");
assertTrue(false);
} else {
System.out.println("read => 1 byte");
assertTrue(remaining > 0);
remaining--;
}
} catch (IOException ioe) {
System.out.format("read => %s%n", ioe);
remaining = 0;
}
}
});
}
/**
* Tests Socket read before available when the connection is reset and there
* are no bytes to read.
*/
public void testSocketReadNoDataBeforeAvailable() throws IOException {
System.out.println("testSocketReadNoDataBeforeAvailable");
withResetConnection(null, sc -> {
Socket s = sc.socket();
InputStream in = s.getInputStream();
for (int i=0; i<REPEAT_COUNT; i++) {
try {
int bytesRead = in.read();
if (bytesRead == -1) {
System.out.println("read => EOF");
} else {
System.out.println("read => 1 byte");
}
assertTrue(false);
} catch (IOException ioe) {
System.out.format("read => %s (expected)%n", ioe);
}
int bytesAvailable = in.available();
System.out.format("available => %d%n", bytesAvailable);
assertTrue(bytesAvailable == 0);
}
});
}
/**
* Tests Socket read before available when the connection is reset and there
* are bytes to read.
*/
public void testSocketReadDataBeforeAvailable() throws IOException {
System.out.println("testSocketReadDataBeforeAvailable");
byte[] data = { 1, 2, 3 };
withResetConnection(data, sc -> {
Socket s = sc.socket();
InputStream in = s.getInputStream();
int remaining = data.length;
for (int i=0; i<REPEAT_COUNT; i++) {
try {
int bytesRead = in.read();
if (bytesRead == -1) {
System.out.println("read => EOF");
assertTrue(false);
} else {
System.out.println("read => 1 byte");
assertTrue(remaining > 0);
remaining--;
}
} catch (IOException ioe) {
System.out.format("read => %s%n", ioe);
remaining = 0;
}
int bytesAvailable = in.available();
System.out.format("available => %d%n", bytesAvailable);
assertTrue(bytesAvailable <= remaining);
}
});
}
interface ThrowingConsumer<T> {
void accept(T t) throws IOException;
}
/**
* Invokes a consumer with a SocketChannel connected to a peer that has closed
* the connection with a "connection reset". The peer sends the given data
* bytes before closing (when data is not null).
*/
static void withResetConnection(byte[] data, ThrowingConsumer<SocketChannel> consumer)
throws IOException
{
var loopback = InetAddress.getLoopbackAddress();
try (var listener = new ServerSocket()) {
listener.bind(new InetSocketAddress(loopback, 0));
try (var sc = SocketChannel.open()) {
sc.connect(listener.getLocalSocketAddress());
try (Socket peer = listener.accept()) {
if (data != null) {
peer.getOutputStream().write(data);
}
peer.setSoLinger(true, 0);
}
consumer.accept(sc);
}
}
}
}