Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 143 additions & 89 deletions test/jdk/java/net/Socket/SocketReadInterruptTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, 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
Expand All @@ -21,58 +21,75 @@
* questions.
*/

/**
/*
* @test
* @bug 8237858
* @summary SocketImpl.socketAccept() handles EINTR incorrectly
* @summary Verify that when an application is blocked in InputStream.read() on a Socket's
* input stream and the native thread doing the read() on the socket's file descriptor
* receives a EINTR, then it won't result in the application receiving an exception
* from InputStream.read().
* @requires (os.family != "windows")
* @compile NativeThread.java
* @run main/othervm/native SocketReadInterruptTest 2000 3000
*
* @comment the second argument to the SocketReadInterruptTest application is a
* reasonably large socket read timeout to exercise a timed wait for the
* InputStream.read() call
* @run main/othervm/native SocketReadInterruptTest 2000 1800000
*
* @comment the second argument, "0", to the SocketReadInterruptTest application is
* to exercise the InputStream.read() without a timeout
* @run main/othervm/native SocketReadInterruptTest 2000 0
*/

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;

import java.net.*;
import java.net.Socket;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static java.nio.charset.StandardCharsets.US_ASCII;

public class SocketReadInterruptTest {

public static void main(String[] args) throws Exception {
System.loadLibrary("NativeThread");
ExecutorService executor = Executors.newFixedThreadPool(2);
InetAddress loopback = InetAddress.getLoopbackAddress();

try ( ServerSocket ss = new ServerSocket(0, 50, loopback);
Socket s1 = new Socket(loopback, ss.getLocalPort());) {
Server server = new Server(ss, Integer.parseInt(args[0]));
Future<Result> f1 = executor.submit(server);

Client client = new Client(s1, Integer.parseInt(args[1]));
Future<Result> f2 = executor.submit(client);
long threadId = client.getID();

ExecutorService executor = Executors.newFixedThreadPool(2);
try (ServerSocket ss = new ServerSocket(0, 0, loopback);
Socket s1 = new Socket(loopback, ss.getLocalPort())) {

// amount of time to wait before writing a response on the
// socket's output stream
int delayBeforeWrite = Integer.parseInt(args[0]);
Server server = new Server(ss, (InetSocketAddress) s1.getLocalSocketAddress(),
delayBeforeWrite);
Future<Void> f1 = executor.submit(server);

// read timeout to be configured on the socket before doing a InputStream.read()
int readTimeout = Integer.parseInt(args[1]);
Client client = new Client(s1, readTimeout);
Future<Void> f2 = executor.submit(client);
// wait for the client thread to be ready to call the socket input stream's read()
client.ready.await();
// wait just some more for the client thread to block on InputStream.read(), before
// we send a signal to interrupt that thread
sleep(200);
System.out.println("Sending SIGPIPE to client thread.");
if (NativeThread.signal(threadId, NativeThread.SIGPIPE) != 0) {
throw new RuntimeException("Failed to interrupt the thread.");
}

Result r1 = f1.get();
if (r1.status == Result.FAIL) {
throw r1.exception;
}

Result r2 = f2.get();
if (r2.status == Result.FAIL) {
throw r2.exception;
long nativeTheadId = client.getThreadId();
System.out.println("Sending SIGPIPE to client thread " + nativeTheadId);
if (NativeThread.signal(nativeTheadId, NativeThread.SIGPIPE) != 0) {
throw new RuntimeException("Failed to interrupt the thread");
}
// wait for the client to complete
f2.get();
// wait for the server to complete
f1.get();
System.out.println("OK!");
} finally {
executor.shutdown();
Expand All @@ -87,93 +104,130 @@ private static void sleep(long time) {
}
}

static class Client implements Callable<Result> {
static class Client implements Callable<Void> {

private volatile long threadId;
private final Socket client;
private final int timeout;
private final CountDownLatch ready = new CountDownLatch(1);
private final Socket socket;
private final int readTimeout;
private volatile long nativeThreadId = -1;

public Client(Socket s, int timeout) {
client = s;
this.timeout = timeout;
public Client(Socket s, int readTimeout) {
this.socket = s;
this.readTimeout = readTimeout;
}

@Override
public Result call() {
threadId = NativeThread.getID();
byte[] arr = new byte[64];
try ( InputStream in = client.getInputStream();) {
client.setSoTimeout(timeout);
in.read(arr);
return new Result(Result.SUCCESS, null);
} catch (IOException ex) {
close();
return new Result(Result.FAIL, ex);
public Void call() throws Exception {
try {
doCall();
return null;
} catch (Throwable t) {
System.err.println("Exception in client: " + t);
t.printStackTrace();
throw t;
}
}

long getID() {
while (threadId == 0) {
sleep(5);
private void doCall() throws Exception {
// capture the native thread id of the current thread
nativeThreadId = NativeThread.getID();
// set a timeout and then read from the socket's input stream
try (InputStream in = socket.getInputStream()) {
socket.setSoTimeout(readTimeout);
int totalRead = 0;
int n = 0;
// let the main thread know that we are about to do a
// InputStream.read() on the socket
ready.countDown();
while ((n = in.read(new byte[100])) != -1) {
totalRead += n;
}
System.out.println("read() completed with " + totalRead + " bytes");
// just the byte count check is OK
if (totalRead != Server.RESPONSE.length) {
throw new AssertionError("unexpected number of bytes read: "
+ totalRead + ", expected: " + Server.RESPONSE.length);
}
}
return threadId;
}

void close() {
if (!client.isClosed()) {
try {
client.close();
} catch (IOException ex) {
// ignore the exception.
}
/**
* Returns the id of thread which is executing the {@link #call()} method
*/
long getThreadId() {
if (ready.getCount() != 0) {
throw new IllegalStateException("Client thread is not yet ready");
}
return nativeThreadId;
}
}

static class Server implements Callable<Result> {

static class Server implements Callable<Void> {
private static final byte[] RESPONSE = "This is just a test string.".getBytes(US_ASCII);
private final ServerSocket serverSocket;
private final int timeout;

public Server(ServerSocket ss, int timeout) {
private final InetSocketAddress expectedClientAddr;
private final int delayBeforeWrite;

/**
* Constructs a server
*
* @param ss The ServerSocket
* @param expectedClientAddr The InetSocketAddress of the socket, constructed in this
* test, from which a connect() is expected
* @param delayBeforeWrite delay in milliseconds before the response is written by the
* server on the accepted socket
*/
public Server(ServerSocket ss, InetSocketAddress expectedClientAddr, int delayBeforeWrite) {
serverSocket = ss;
this.timeout = timeout;
this.expectedClientAddr = expectedClientAddr;
this.delayBeforeWrite = delayBeforeWrite;
}

@Override
public Result call() {
public Void call() throws Exception {
try {
try ( Socket client = serverSocket.accept(); OutputStream outputStream = client.getOutputStream();) {
sleep(timeout);
outputStream.write("This is just a test string.".getBytes());
return new Result(Result.SUCCESS, null);
}
} catch (IOException e) {
close();
return new Result(Result.FAIL, e);
doCall();
return null;
} catch (Throwable t) {
System.err.println("Exception in server: " + t);
t.printStackTrace();
throw t;
}
}

public void close() {
if (!serverSocket.isClosed()) {
try {
serverSocket.close();
} catch (IOException ex) {
private void doCall() throws Exception {
System.out.println("server listening at " + serverSocket);
while (true) {
System.out.println("waiting for connection from " + this.expectedClientAddr);
Socket client = this.serverSocket.accept();
System.out.println("accepted connection from " + client);
// if the connection is from some unexpected client, then close
// it and wait for a connection from this test's client
if (!this.expectedClientAddr.equals(client.getRemoteSocketAddress())) {
System.out.println("closing unexpected connection from " + client);
closeQuietly(client);
continue;
}
sendResponse(client);
// we don't expect any more connection from this test
return;
}
}
}

static class Result {

static final int SUCCESS = 0;
static final int FAIL = 1;
final int status;
final Exception exception;
private static void closeQuietly(Socket socket) {
try {
socket.close();
} catch (IOException ioe) {
// ignore
}
}

public Result(int status, Exception ex) {
this.status = status;
exception = ex;
private void sendResponse(final Socket client) throws IOException {
try (OutputStream outputStream = client.getOutputStream()) {
sleep(delayBeforeWrite);
System.out.println("Sending " + RESPONSE.length + " bytes of response to " + client);
outputStream.write(RESPONSE);
}
}
}
}
}