21a59b9f4e
Reviewed-by: djelinski
741 lines
23 KiB
Java
741 lines
23 KiB
Java
/*
|
|
* Copyright (c) 2018, 2023, 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 id=default
|
|
* @bug 8284161
|
|
* @summary Test virtual threads doing blocking I/O on java.net Sockets
|
|
* @library /test/lib
|
|
* @run junit BlockingSocketOps
|
|
*/
|
|
|
|
/**
|
|
* @test id=poller-modes
|
|
* @requires (os.family == "linux") | (os.family == "mac")
|
|
* @library /test/lib
|
|
* @run junit/othervm -Djdk.pollerMode=1 BlockingSocketOps
|
|
* @run junit/othervm -Djdk.pollerMode=2 BlockingSocketOps
|
|
*/
|
|
|
|
/**
|
|
* @test id=no-vmcontinuations
|
|
* @requires vm.continuations
|
|
* @library /test/lib
|
|
* @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations BlockingSocketOps
|
|
*/
|
|
|
|
import java.io.Closeable;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.DatagramPacket;
|
|
import java.net.DatagramSocket;
|
|
import java.net.InetAddress;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.ServerSocket;
|
|
import java.net.Socket;
|
|
import java.net.SocketAddress;
|
|
import java.net.SocketException;
|
|
import java.net.SocketTimeoutException;
|
|
|
|
import jdk.test.lib.thread.VThreadRunner;
|
|
import org.junit.jupiter.api.Test;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
class BlockingSocketOps {
|
|
|
|
/**
|
|
* Socket read/write, no blocking.
|
|
*/
|
|
@Test
|
|
void testSocketReadWrite1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s1 = connection.socket1();
|
|
Socket s2 = connection.socket2();
|
|
|
|
// write should not block
|
|
byte[] ba = "XXX".getBytes("UTF-8");
|
|
s1.getOutputStream().write(ba);
|
|
|
|
// read should not block
|
|
ba = new byte[10];
|
|
int n = s2.getInputStream().read(ba);
|
|
assertTrue(n > 0);
|
|
assertTrue(ba[0] == 'X');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in read.
|
|
*/
|
|
@Test
|
|
void testSocketRead1() throws Exception {
|
|
testSocketRead(0);
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in timed read.
|
|
*/
|
|
@Test
|
|
void testSocketRead2() throws Exception {
|
|
testSocketRead(60_000);
|
|
}
|
|
|
|
void testSocketRead(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s1 = connection.socket1();
|
|
Socket s2 = connection.socket2();
|
|
|
|
// delayed write from sc1
|
|
byte[] ba1 = "XXX".getBytes("UTF-8");
|
|
runAfterParkedAsync(() -> s1.getOutputStream().write(ba1));
|
|
|
|
// read from sc2 should block
|
|
if (timeout > 0) {
|
|
s2.setSoTimeout(timeout);
|
|
}
|
|
byte[] ba2 = new byte[10];
|
|
int n = s2.getInputStream().read(ba2);
|
|
assertTrue(n > 0);
|
|
assertTrue(ba2[0] == 'X');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in write.
|
|
*/
|
|
@Test
|
|
void testSocketWrite1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s1 = connection.socket1();
|
|
Socket s2 = connection.socket2();
|
|
|
|
// delayed read from s2 to EOF
|
|
InputStream in = s2.getInputStream();
|
|
Thread reader = runAfterParkedAsync(() ->
|
|
in.transferTo(OutputStream.nullOutputStream()));
|
|
|
|
// write should block
|
|
byte[] ba = new byte[100*1024];
|
|
try (OutputStream out = s1.getOutputStream()) {
|
|
for (int i = 0; i < 1000; i++) {
|
|
out.write(ba);
|
|
}
|
|
}
|
|
|
|
// wait for reader to finish
|
|
reader.join();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in read, peer closes connection gracefully.
|
|
*/
|
|
@Test
|
|
void testSocketReadPeerClose1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s1 = connection.socket1();
|
|
Socket s2 = connection.socket2();
|
|
|
|
// delayed close of s2
|
|
runAfterParkedAsync(s2::close);
|
|
|
|
// read from s1 should block, then read -1
|
|
int n = s1.getInputStream().read();
|
|
assertTrue(n == -1);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in read, peer closes connection abruptly.
|
|
*/
|
|
@Test
|
|
void testSocketReadPeerClose2() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s1 = connection.socket1();
|
|
Socket s2 = connection.socket2();
|
|
|
|
// delayed abrupt close of s2
|
|
s2.setSoLinger(true, 0);
|
|
runAfterParkedAsync(s2::close);
|
|
|
|
// read from s1 should block, then throw
|
|
try {
|
|
int n = s1.getInputStream().read();
|
|
fail("read " + n);
|
|
} catch (IOException ioe) {
|
|
// expected
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Socket close while virtual thread blocked in read.
|
|
*/
|
|
@Test
|
|
void testSocketReadAsyncClose1() throws Exception {
|
|
testSocketReadAsyncClose(0);
|
|
}
|
|
|
|
/**
|
|
* Socket close while virtual thread blocked in timed read.
|
|
*/
|
|
@Test
|
|
void testSocketReadAsyncClose2() throws Exception {
|
|
testSocketReadAsyncClose(0);
|
|
}
|
|
|
|
void testSocketReadAsyncClose(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s = connection.socket1();
|
|
|
|
// delayed close of s
|
|
runAfterParkedAsync(s::close);
|
|
|
|
// read from s should block, then throw
|
|
if (timeout > 0) {
|
|
s.setSoTimeout(timeout);
|
|
}
|
|
try {
|
|
int n = s.getInputStream().read();
|
|
fail("read " + n);
|
|
} catch (SocketException expected) { }
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread interrupted while blocked in Socket read.
|
|
*/
|
|
@Test
|
|
void testSocketReadInterrupt1() throws Exception {
|
|
testSocketReadInterrupt(0);
|
|
}
|
|
|
|
/**
|
|
* Virtual thread interrupted while blocked in Socket read with timeout
|
|
*/
|
|
@Test
|
|
void testSocketReadInterrupt2() throws Exception {
|
|
testSocketReadInterrupt(60_000);
|
|
}
|
|
|
|
void testSocketReadInterrupt(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s = connection.socket1();
|
|
|
|
|
|
// delayed interrupt of current thread
|
|
Thread thisThread = Thread.currentThread();
|
|
runAfterParkedAsync(thisThread::interrupt);
|
|
|
|
// read from s should block, then throw
|
|
if (timeout > 0) {
|
|
s.setSoTimeout(timeout);
|
|
}
|
|
try {
|
|
int n = s.getInputStream().read();
|
|
fail("read " + n);
|
|
} catch (SocketException expected) {
|
|
assertTrue(Thread.interrupted());
|
|
assertTrue(s.isClosed());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Socket close while virtual thread blocked in write.
|
|
*/
|
|
@Test
|
|
void testSocketWriteAsyncClose() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s = connection.socket1();
|
|
|
|
// delayedclose of s
|
|
runAfterParkedAsync(s::close);
|
|
|
|
// write to s should block, then throw
|
|
try {
|
|
byte[] ba = new byte[100*1024];
|
|
OutputStream out = s.getOutputStream();
|
|
for (;;) {
|
|
out.write(ba);
|
|
}
|
|
} catch (SocketException expected) { }
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread interrupted while blocked in Socket write.
|
|
*/
|
|
@Test
|
|
void testSocketWriteInterrupt() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s = connection.socket1();
|
|
|
|
// delayed interrupt of current thread
|
|
Thread thisThread = Thread.currentThread();
|
|
runAfterParkedAsync(thisThread::interrupt);
|
|
|
|
// write to s should block, then throw
|
|
try {
|
|
byte[] ba = new byte[100*1024];
|
|
OutputStream out = s.getOutputStream();
|
|
for (;;) {
|
|
out.write(ba);
|
|
}
|
|
} catch (SocketException expected) {
|
|
assertTrue(Thread.interrupted());
|
|
assertTrue(s.isClosed());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread reading urgent data when SO_OOBINLINE is enabled.
|
|
*/
|
|
@Test
|
|
void testSocketReadUrgentData() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var connection = new Connection()) {
|
|
Socket s1 = connection.socket1();
|
|
Socket s2 = connection.socket2();
|
|
|
|
// urgent data should be received
|
|
runAfterParkedAsync(() -> s2.sendUrgentData('X'));
|
|
|
|
// read should block, then read the OOB byte
|
|
s1.setOOBInline(true);
|
|
byte[] ba = new byte[10];
|
|
int n = s1.getInputStream().read(ba);
|
|
assertTrue(n == 1);
|
|
assertTrue(ba[0] == 'X');
|
|
|
|
// urgent data should not be received
|
|
s1.setOOBInline(false);
|
|
s1.setSoTimeout(500);
|
|
s2.sendUrgentData('X');
|
|
try {
|
|
s1.getInputStream().read(ba);
|
|
fail();
|
|
} catch (SocketTimeoutException expected) { }
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* ServerSocket accept, no blocking.
|
|
*/
|
|
@Test
|
|
void testServerSocketAccept1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var listener = new ServerSocket()) {
|
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
|
listener.bind(new InetSocketAddress(loopback, 0));
|
|
|
|
// establish connection
|
|
var socket1 = new Socket(loopback, listener.getLocalPort());
|
|
|
|
// accept should not block
|
|
var socket2 = listener.accept();
|
|
socket1.close();
|
|
socket2.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in accept.
|
|
*/
|
|
@Test
|
|
void testServerSocketAccept2() throws Exception {
|
|
testServerSocketAccept(0);
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in timed accept.
|
|
*/
|
|
@Test
|
|
void testServerSocketAccept3() throws Exception {
|
|
testServerSocketAccept(60_000);
|
|
}
|
|
|
|
void testServerSocketAccept(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var listener = new ServerSocket()) {
|
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
|
listener.bind(new InetSocketAddress(loopback, 0));
|
|
|
|
// schedule connect
|
|
var socket1 = new Socket();
|
|
SocketAddress remote = listener.getLocalSocketAddress();
|
|
runAfterParkedAsync(() -> socket1.connect(remote));
|
|
|
|
// accept should block
|
|
if (timeout > 0) {
|
|
listener.setSoTimeout(timeout);
|
|
}
|
|
var socket2 = listener.accept();
|
|
socket1.close();
|
|
socket2.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* ServerSocket close while virtual thread blocked in accept.
|
|
*/
|
|
@Test
|
|
void testServerSocketAcceptAsyncClose1() throws Exception {
|
|
testServerSocketAcceptAsyncClose(0);
|
|
}
|
|
|
|
/**
|
|
* ServerSocket close while virtual thread blocked in timed accept.
|
|
*/
|
|
@Test
|
|
void testServerSocketAcceptAsyncClose2() throws Exception {
|
|
testServerSocketAcceptAsyncClose(60_000);
|
|
}
|
|
|
|
void testServerSocketAcceptAsyncClose(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var listener = new ServerSocket()) {
|
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
|
listener.bind(new InetSocketAddress(loopback, 0));
|
|
|
|
// delayed close of listener
|
|
runAfterParkedAsync(listener::close);
|
|
|
|
// accept should block, then throw
|
|
if (timeout > 0) {
|
|
listener.setSoTimeout(timeout);
|
|
}
|
|
try {
|
|
listener.accept().close();
|
|
fail("connection accepted???");
|
|
} catch (SocketException expected) { }
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread interrupted while blocked in ServerSocket accept.
|
|
*/
|
|
@Test
|
|
void testServerSocketAcceptInterrupt1() throws Exception {
|
|
testServerSocketAcceptInterrupt(0);
|
|
}
|
|
|
|
/**
|
|
* Virtual thread interrupted while blocked in ServerSocket accept with timeout.
|
|
*/
|
|
@Test
|
|
void testServerSocketAcceptInterrupt2() throws Exception {
|
|
testServerSocketAcceptInterrupt(60_000);
|
|
}
|
|
|
|
void testServerSocketAcceptInterrupt(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (var listener = new ServerSocket()) {
|
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
|
listener.bind(new InetSocketAddress(loopback, 0));
|
|
|
|
// delayed interrupt of current thread
|
|
Thread thisThread = Thread.currentThread();
|
|
runAfterParkedAsync(thisThread::interrupt);
|
|
|
|
// accept should block, then throw
|
|
if (timeout > 0) {
|
|
listener.setSoTimeout(timeout);
|
|
}
|
|
try {
|
|
listener.accept().close();
|
|
fail("connection accepted???");
|
|
} catch (SocketException expected) {
|
|
assertTrue(Thread.interrupted());
|
|
assertTrue(listener.isClosed());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* DatagramSocket receive/send, no blocking.
|
|
*/
|
|
@Test
|
|
void testDatagramSocketSendReceive1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (DatagramSocket s1 = new DatagramSocket(null);
|
|
DatagramSocket s2 = new DatagramSocket(null)) {
|
|
|
|
InetAddress lh = InetAddress.getLoopbackAddress();
|
|
s1.bind(new InetSocketAddress(lh, 0));
|
|
s2.bind(new InetSocketAddress(lh, 0));
|
|
|
|
// send should not block
|
|
byte[] bytes = "XXX".getBytes("UTF-8");
|
|
DatagramPacket p1 = new DatagramPacket(bytes, bytes.length);
|
|
p1.setSocketAddress(s2.getLocalSocketAddress());
|
|
s1.send(p1);
|
|
|
|
// receive should not block
|
|
byte[] ba = new byte[100];
|
|
DatagramPacket p2 = new DatagramPacket(ba, ba.length);
|
|
s2.receive(p2);
|
|
assertEquals(s1.getLocalSocketAddress(), p2.getSocketAddress());
|
|
assertTrue(ba[0] == 'X');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in DatagramSocket receive.
|
|
*/
|
|
@Test
|
|
void testDatagramSocketSendReceive2() throws Exception {
|
|
testDatagramSocketSendReceive(0);
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in DatagramSocket receive with timeout.
|
|
*/
|
|
@Test
|
|
void testDatagramSocketSendReceive3() throws Exception {
|
|
testDatagramSocketSendReceive(60_000);
|
|
}
|
|
|
|
private void testDatagramSocketSendReceive(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (DatagramSocket s1 = new DatagramSocket(null);
|
|
DatagramSocket s2 = new DatagramSocket(null)) {
|
|
|
|
InetAddress lh = InetAddress.getLoopbackAddress();
|
|
s1.bind(new InetSocketAddress(lh, 0));
|
|
s2.bind(new InetSocketAddress(lh, 0));
|
|
|
|
// delayed send
|
|
byte[] bytes = "XXX".getBytes("UTF-8");
|
|
DatagramPacket p1 = new DatagramPacket(bytes, bytes.length);
|
|
p1.setSocketAddress(s2.getLocalSocketAddress());
|
|
runAfterParkedAsync(() -> s1.send(p1));
|
|
|
|
// receive should block
|
|
if (timeout > 0) {
|
|
s2.setSoTimeout(timeout);
|
|
}
|
|
byte[] ba = new byte[100];
|
|
DatagramPacket p2 = new DatagramPacket(ba, ba.length);
|
|
s2.receive(p2);
|
|
assertEquals(s1.getLocalSocketAddress(), p2.getSocketAddress());
|
|
assertTrue(ba[0] == 'X');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread blocks in DatagramSocket receive that times out.
|
|
*/
|
|
@Test
|
|
void testDatagramSocketReceiveTimeout() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (DatagramSocket s = new DatagramSocket(null)) {
|
|
InetAddress lh = InetAddress.getLoopbackAddress();
|
|
s.bind(new InetSocketAddress(lh, 0));
|
|
s.setSoTimeout(500);
|
|
byte[] ba = new byte[100];
|
|
DatagramPacket p = new DatagramPacket(ba, ba.length);
|
|
try {
|
|
s.receive(p);
|
|
fail();
|
|
} catch (SocketTimeoutException expected) { }
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* DatagramSocket close while virtual thread blocked in receive.
|
|
*/
|
|
@Test
|
|
void testDatagramSocketReceiveAsyncClose1() throws Exception {
|
|
testDatagramSocketReceiveAsyncClose(0);
|
|
}
|
|
|
|
/**
|
|
* DatagramSocket close while virtual thread blocked with timeout.
|
|
*/
|
|
@Test
|
|
void testDatagramSocketReceiveAsyncClose2() throws Exception {
|
|
testDatagramSocketReceiveAsyncClose(60_000);
|
|
}
|
|
|
|
private void testDatagramSocketReceiveAsyncClose(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (DatagramSocket s = new DatagramSocket(null)) {
|
|
InetAddress lh = InetAddress.getLoopbackAddress();
|
|
s.bind(new InetSocketAddress(lh, 0));
|
|
|
|
// delayed close of s
|
|
runAfterParkedAsync(s::close);
|
|
|
|
// receive should block, then throw
|
|
if (timeout > 0) {
|
|
s.setSoTimeout(timeout);
|
|
}
|
|
try {
|
|
byte[] ba = new byte[100];
|
|
DatagramPacket p = new DatagramPacket(ba, ba.length);
|
|
s.receive(p);
|
|
fail();
|
|
} catch (SocketException expected) { }
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Virtual thread interrupted while blocked in DatagramSocket receive.
|
|
*/
|
|
@Test
|
|
void testDatagramSocketReceiveInterrupt1() throws Exception {
|
|
testDatagramSocketReceiveInterrupt(0);
|
|
}
|
|
|
|
/**
|
|
* Virtual thread interrupted while blocked in DatagramSocket receive with timeout.
|
|
*/
|
|
@Test
|
|
void testDatagramSocketReceiveInterrupt2() throws Exception {
|
|
testDatagramSocketReceiveInterrupt(60_000);
|
|
}
|
|
|
|
private void testDatagramSocketReceiveInterrupt(int timeout) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
try (DatagramSocket s = new DatagramSocket(null)) {
|
|
InetAddress lh = InetAddress.getLoopbackAddress();
|
|
s.bind(new InetSocketAddress(lh, 0));
|
|
|
|
// delayed interrupt of current thread
|
|
Thread thisThread = Thread.currentThread();
|
|
runAfterParkedAsync(thisThread::interrupt);
|
|
|
|
// receive should block, then throw
|
|
if (timeout > 0) {
|
|
s.setSoTimeout(timeout);
|
|
}
|
|
try {
|
|
byte[] ba = new byte[100];
|
|
DatagramPacket p = new DatagramPacket(ba, ba.length);
|
|
s.receive(p);
|
|
fail();
|
|
} catch (SocketException expected) {
|
|
assertTrue(Thread.interrupted());
|
|
assertTrue(s.isClosed());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a loopback connection
|
|
*/
|
|
static class Connection implements Closeable {
|
|
private final Socket s1;
|
|
private final Socket s2;
|
|
Connection() throws IOException {
|
|
var lh = InetAddress.getLoopbackAddress();
|
|
try (var listener = new ServerSocket()) {
|
|
listener.bind(new InetSocketAddress(lh, 0));
|
|
Socket s1 = new Socket();
|
|
Socket s2;
|
|
try {
|
|
s1.connect(listener.getLocalSocketAddress());
|
|
s2 = listener.accept();
|
|
} catch (IOException ioe) {
|
|
s1.close();
|
|
throw ioe;
|
|
}
|
|
this.s1 = s1;
|
|
this.s2 = s2;
|
|
}
|
|
|
|
}
|
|
Socket socket1() {
|
|
return s1;
|
|
}
|
|
Socket socket2() {
|
|
return s2;
|
|
}
|
|
@Override
|
|
public void close() throws IOException {
|
|
s1.close();
|
|
s2.close();
|
|
}
|
|
}
|
|
|
|
@FunctionalInterface
|
|
interface ThrowingRunnable {
|
|
void run() throws Exception;
|
|
}
|
|
|
|
/**
|
|
* Runs the given task asynchronously after the current virtual thread has parked.
|
|
* @return the thread started to run the task
|
|
*/
|
|
static Thread runAfterParkedAsync(ThrowingRunnable task) {
|
|
Thread target = Thread.currentThread();
|
|
if (!target.isVirtual())
|
|
throw new WrongThreadException();
|
|
return Thread.ofPlatform().daemon().start(() -> {
|
|
try {
|
|
Thread.State state = target.getState();
|
|
while (state != Thread.State.WAITING
|
|
&& state != Thread.State.TIMED_WAITING) {
|
|
Thread.sleep(20);
|
|
state = target.getState();
|
|
}
|
|
Thread.sleep(20); // give a bit more time to release carrier
|
|
task.run();
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
});
|
|
}
|
|
}
|