1 /*
  2  * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test id=default
 26  * @bug 8284161
 27  * @summary Test virtual threads doing blocking I/O on java.net Sockets
 28  * @library /test/lib
 29  * @run junit BlockingSocketOps
 30  */
 31 
 32 /*
 33  * @test id=poller-modes
 34  * @requires (os.family == "linux") | (os.family == "mac")
 35  * @library /test/lib
 36  * @run junit/othervm -Djdk.pollerMode=1 BlockingSocketOps
 37  * @run junit/othervm -Djdk.pollerMode=2 BlockingSocketOps
 38  * @run junit/othervm -Djdk.pollerMode=3 BlockingSocketOps
 39  */
 40 
 41 /*
 42  * @test id=io_uring
 43  * @requires os.family == "linux"
 44  * @library /test/lib
 45  * @run junit/othervm -Djdk.pollerMode=1 -Djdk.io_uring=true BlockingSocketOps
 46  * @run junit/othervm -Djdk.pollerMode=2 -Djdk.io_uring=true BlockingSocketOps
 47  * @run junit/othervm -Djdk.pollerMode=3 -Djdk.io_uring=true BlockingSocketOps
 48  */
 49 
 50 /*
 51  * @test id=no-vmcontinuations
 52  * @requires vm.continuations
 53  * @library /test/lib
 54  * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations BlockingSocketOps
 55  */
 56 
 57 import java.io.Closeable;
 58 import java.io.IOException;
 59 import java.io.InputStream;
 60 import java.io.OutputStream;
 61 import java.net.DatagramPacket;
 62 import java.net.DatagramSocket;
 63 import java.net.InetAddress;
 64 import java.net.InetSocketAddress;
 65 import java.net.ServerSocket;
 66 import java.net.Socket;
 67 import java.net.SocketAddress;
 68 import java.net.SocketException;
 69 import java.net.SocketTimeoutException;
 70 
 71 import jdk.test.lib.thread.VThreadRunner;
 72 import org.junit.jupiter.api.Test;
 73 import static org.junit.jupiter.api.Assertions.*;
 74 
 75 class BlockingSocketOps {
 76 
 77     /**
 78      * Socket read/write, no blocking.
 79      */
 80     @Test
 81     void testSocketReadWrite1() throws Exception {
 82         VThreadRunner.run(() -> {
 83             try (var connection = new Connection()) {
 84                 Socket s1 = connection.socket1();
 85                 Socket s2 = connection.socket2();
 86 
 87                 // write should not block
 88                 byte[] ba = "XXX".getBytes("UTF-8");
 89                 s1.getOutputStream().write(ba);
 90 
 91                 // read should not block
 92                 ba = new byte[10];
 93                 int n = s2.getInputStream().read(ba);
 94                 assertTrue(n > 0);
 95                 assertTrue(ba[0] == 'X');
 96             }
 97         });
 98     }
 99 
100     /**
101      * Virtual thread blocks in read.
102      */
103     @Test
104     void testSocketRead1() throws Exception {
105         testSocketRead(0);
106     }
107 
108     /**
109      * Virtual thread blocks in timed read.
110      */
111     @Test
112     void testSocketRead2() throws Exception {
113         testSocketRead(60_000);
114     }
115 
116     void testSocketRead(int timeout) throws Exception {
117         VThreadRunner.run(() -> {
118             try (var connection = new Connection()) {
119                 Socket s1 = connection.socket1();
120                 Socket s2 = connection.socket2();
121 
122                 // delayed write from sc1
123                 byte[] ba1 = "XXX".getBytes("UTF-8");
124                 runAfterParkedAsync(() -> s1.getOutputStream().write(ba1));
125 
126                 // read from sc2 should block
127                 if (timeout > 0) {
128                     s2.setSoTimeout(timeout);
129                 }
130                 byte[] ba2 = new byte[10];
131                 int n = s2.getInputStream().read(ba2);
132                 assertTrue(n > 0);
133                 assertTrue(ba2[0] == 'X');
134             }
135         });
136     }
137 
138     /**
139      * Virtual thread blocks in write.
140      */
141     @Test
142     void testSocketWrite1() throws Exception {
143         VThreadRunner.run(() -> {
144             try (var connection = new Connection()) {
145                 Socket s1 = connection.socket1();
146                 Socket s2 = connection.socket2();
147 
148                 // delayed read from s2 to EOF
149                 InputStream in = s2.getInputStream();
150                 Thread reader = runAfterParkedAsync(() ->
151                         in.transferTo(OutputStream.nullOutputStream()));
152 
153                 // write should block
154                 byte[] ba = new byte[100*1024];
155                 try (OutputStream out = s1.getOutputStream()) {
156                     for (int i = 0; i < 1000; i++) {
157                         out.write(ba);
158                     }
159                 }
160 
161                 // wait for reader to finish
162                 reader.join();
163             }
164         });
165     }
166 
167     /**
168      * Virtual thread blocks in read, peer closes connection gracefully.
169      */
170     @Test
171     void testSocketReadPeerClose1() throws Exception {
172         VThreadRunner.run(() -> {
173             try (var connection = new Connection()) {
174                 Socket s1 = connection.socket1();
175                 Socket s2 = connection.socket2();
176 
177                 // delayed close of s2
178                 runAfterParkedAsync(s2::close);
179 
180                 // read from s1 should block, then read -1
181                 int n = s1.getInputStream().read();
182                 assertTrue(n == -1);
183             }
184         });
185     }
186 
187     /**
188      * Virtual thread blocks in read, peer closes connection abruptly.
189      */
190     @Test
191     void testSocketReadPeerClose2() throws Exception {
192         VThreadRunner.run(() -> {
193             try (var connection = new Connection()) {
194                 Socket s1 = connection.socket1();
195                 Socket s2 = connection.socket2();
196 
197                 // delayed abrupt close of s2
198                 s2.setSoLinger(true, 0);
199                 runAfterParkedAsync(s2::close);
200 
201                 // read from s1 should block, then throw
202                 try {
203                     int n = s1.getInputStream().read();
204                     fail("read " + n);
205                 } catch (IOException ioe) {
206                     // expected
207                 }
208             }
209         });
210     }
211 
212     /**
213      * Socket close while virtual thread blocked in read.
214      */
215     @Test
216     void testSocketReadAsyncClose1() throws Exception {
217         testSocketReadAsyncClose(0);
218     }
219 
220     /**
221      * Socket close while virtual thread blocked in timed read.
222      */
223     @Test
224     void testSocketReadAsyncClose2() throws Exception {
225         testSocketReadAsyncClose(60_000);
226     }
227 
228     void testSocketReadAsyncClose(int timeout) throws Exception {
229         VThreadRunner.run(() -> {
230             try (var connection = new Connection()) {
231                 Socket s = connection.socket1();
232 
233                 // delayed close of s
234                 runAfterParkedAsync(s::close);
235 
236                 // read from s should block, then throw
237                 if (timeout > 0) {
238                     s.setSoTimeout(timeout);
239                 }
240                 try {
241                     int n = s.getInputStream().read();
242                     fail("read " + n);
243                 } catch (SocketException expected) { }
244             }
245         });
246     }
247 
248     /**
249      * Socket shutdownInput while virtual thread blocked in read.
250      */
251     @Test
252     void testSocketReadAsyncShutdownInput1() throws Exception {
253         testSocketReadAsyncShutdownInput(0);
254     }
255 
256     /**
257      * Socket shutdownInput while virtual thread blocked in timed read.
258      */
259     @Test
260     void testSocketReadAsyncShutdownInput2() throws Exception {
261         testSocketReadAsyncShutdownInput(60_000);
262     }
263 
264     void testSocketReadAsyncShutdownInput(int timeout) throws Exception {
265         VThreadRunner.run(() -> {
266             try (var connection = new Connection()) {
267                 Socket s = connection.socket1();
268 
269                 // delayed shutdown of s
270                 runAfterParkedAsync(s::shutdownInput);
271 
272                 // read from s should block, then throw
273                 if (timeout > 0) {
274                     s.setSoTimeout(timeout);
275                 }
276 
277                 // -1 or SocketException
278                 try {
279                     int n = s.getInputStream().read();
280                     assertEquals(-1, n);
281                 } catch (SocketException e) { }
282                 assertFalse(s.isClosed());
283             }
284         });
285     }
286 
287     /**
288      * Virtual thread interrupted while blocked in Socket read.
289      */
290     @Test
291     void testSocketReadInterrupt1() throws Exception {
292         testSocketReadInterrupt(0);
293     }
294 
295     /**
296      * Virtual thread interrupted while blocked in Socket read with timeout
297      */
298     @Test
299     void testSocketReadInterrupt2() throws Exception {
300         testSocketReadInterrupt(60_000);
301     }
302 
303     void testSocketReadInterrupt(int timeout) throws Exception {
304         VThreadRunner.run(() -> {
305             try (var connection = new Connection()) {
306                 Socket s = connection.socket1();
307 
308 
309                 // delayed interrupt of current thread
310                 Thread thisThread = Thread.currentThread();
311                 runAfterParkedAsync(thisThread::interrupt);
312 
313                 // read from s should block, then throw
314                 if (timeout > 0) {
315                     s.setSoTimeout(timeout);
316                 }
317                 try {
318                     int n = s.getInputStream().read();
319                     fail("read " + n);
320                 } catch (SocketException expected) {
321                     assertTrue(Thread.interrupted());
322                     assertTrue(s.isClosed());
323                 }
324             }
325         });
326     }
327 
328     /**
329      * Socket close while virtual thread blocked in write.
330      */
331     @Test
332     void testSocketWriteAsyncClose() throws Exception {
333         VThreadRunner.run(() -> {
334             try (var connection = new Connection()) {
335                 Socket s = connection.socket1();
336 
337                 // delayed close of s
338                 runAfterParkedAsync(s::close);
339 
340                 // write to s should block, then throw
341                 try {
342                     byte[] ba = new byte[100*1024];
343                     OutputStream out = s.getOutputStream();
344                     for (;;) {
345                         out.write(ba);
346                     }
347                 } catch (SocketException expected) { }
348             }
349         });
350     }
351 
352     /**
353      * Socket shutdownOutput while virtual thread blocked in write.
354      */
355     @Test
356     void testSocketWriteAsyncShutdownOutput() throws Exception {
357         VThreadRunner.run(() -> {
358             try (var connection = new Connection()) {
359                 Socket s = connection.socket1();
360 
361                 // delayed shutdown of s
362                 runAfterParkedAsync(s::shutdownOutput);
363 
364                 // write to s should block, then throw
365                 try {
366                     byte[] ba = new byte[100*1024];
367                     OutputStream out = s.getOutputStream();
368                     for (;;) {
369                         out.write(ba);
370                     }
371                 } catch (SocketException expected) { }
372                 assertFalse(s.isClosed());
373             }
374         });
375     }
376 
377     /**
378      * Virtual thread interrupted while blocked in Socket write.
379      */
380     @Test
381     void testSocketWriteInterrupt() throws Exception {
382         VThreadRunner.run(() -> {
383             try (var connection = new Connection()) {
384                 Socket s = connection.socket1();
385 
386                 // delayed interrupt of current thread
387                 Thread thisThread = Thread.currentThread();
388                 runAfterParkedAsync(thisThread::interrupt);
389 
390                 // write to s should block, then throw
391                 try {
392                     byte[] ba = new byte[100*1024];
393                     OutputStream out = s.getOutputStream();
394                     for (;;) {
395                         out.write(ba);
396                     }
397                 } catch (SocketException expected) {
398                     assertTrue(Thread.interrupted());
399                     assertTrue(s.isClosed());
400                 }
401             }
402         });
403     }
404 
405     /**
406      * Virtual thread reading urgent data when SO_OOBINLINE is enabled.
407      */
408     @Test
409     void testSocketReadUrgentData() throws Exception {
410         VThreadRunner.run(() -> {
411             try (var connection = new Connection()) {
412                 Socket s1 = connection.socket1();
413                 Socket s2 = connection.socket2();
414 
415                 // urgent data should be received
416                 runAfterParkedAsync(() -> s2.sendUrgentData('X'));
417 
418                 // read should block, then read the OOB byte
419                 s1.setOOBInline(true);
420                 byte[] ba = new byte[10];
421                 int n = s1.getInputStream().read(ba);
422                 assertTrue(n == 1);
423                 assertTrue(ba[0] == 'X');
424 
425                 // urgent data should not be received
426                 s1.setOOBInline(false);
427                 s1.setSoTimeout(500);
428                 s2.sendUrgentData('X');
429                 try {
430                     s1.getInputStream().read(ba);
431                     fail();
432                 } catch (SocketTimeoutException expected) { }
433             }
434         });
435     }
436 
437     /**
438      * ServerSocket accept, no blocking.
439      */
440     @Test
441     void testServerSocketAccept1() throws Exception {
442         VThreadRunner.run(() -> {
443             try (var listener = new ServerSocket()) {
444                 InetAddress loopback = InetAddress.getLoopbackAddress();
445                 listener.bind(new InetSocketAddress(loopback, 0));
446 
447                 // establish connection
448                 var socket1 = new Socket(loopback, listener.getLocalPort());
449 
450                 // accept should not block
451                 var socket2 = listener.accept();
452                 socket1.close();
453                 socket2.close();
454             }
455         });
456     }
457 
458     /**
459      * Virtual thread blocks in accept.
460      */
461     @Test
462     void testServerSocketAccept2() throws Exception {
463         testServerSocketAccept(0);
464     }
465 
466     /**
467      * Virtual thread blocks in timed accept.
468      */
469     @Test
470     void testServerSocketAccept3() throws Exception {
471         testServerSocketAccept(60_000);
472     }
473 
474     void testServerSocketAccept(int timeout) throws Exception {
475         VThreadRunner.run(() -> {
476             try (var listener = new ServerSocket()) {
477                 InetAddress loopback = InetAddress.getLoopbackAddress();
478                 listener.bind(new InetSocketAddress(loopback, 0));
479 
480                 // schedule connect
481                 var socket1 = new Socket();
482                 SocketAddress remote = listener.getLocalSocketAddress();
483                 runAfterParkedAsync(() -> socket1.connect(remote));
484 
485                 // accept should block
486                 if (timeout > 0) {
487                     listener.setSoTimeout(timeout);
488                 }
489                 var socket2 = listener.accept();
490                 socket1.close();
491                 socket2.close();
492             }
493         });
494     }
495 
496     /**
497      * ServerSocket close while virtual thread blocked in accept.
498      */
499     @Test
500     void testServerSocketAcceptAsyncClose1() throws Exception {
501         testServerSocketAcceptAsyncClose(0);
502     }
503 
504     /**
505      * ServerSocket close while virtual thread blocked in timed accept.
506      */
507     @Test
508     void testServerSocketAcceptAsyncClose2() throws Exception {
509         testServerSocketAcceptAsyncClose(60_000);
510     }
511 
512     void testServerSocketAcceptAsyncClose(int timeout) throws Exception {
513         VThreadRunner.run(() -> {
514             try (var listener = new ServerSocket()) {
515                 InetAddress loopback = InetAddress.getLoopbackAddress();
516                 listener.bind(new InetSocketAddress(loopback, 0));
517 
518                 // delayed close of listener
519                 runAfterParkedAsync(listener::close);
520 
521                 // accept should block, then throw
522                 if (timeout > 0) {
523                     listener.setSoTimeout(timeout);
524                 }
525                 try {
526                     listener.accept().close();
527                     fail("connection accepted???");
528                 } catch (SocketException expected) { }
529             }
530         });
531     }
532 
533     /**
534      * Virtual thread interrupted while blocked in ServerSocket accept.
535      */
536     @Test
537     void testServerSocketAcceptInterrupt1() throws Exception {
538         testServerSocketAcceptInterrupt(0);
539     }
540 
541     /**
542      * Virtual thread interrupted while blocked in ServerSocket accept with timeout.
543      */
544     @Test
545     void testServerSocketAcceptInterrupt2() throws Exception {
546         testServerSocketAcceptInterrupt(60_000);
547     }
548 
549     void testServerSocketAcceptInterrupt(int timeout) throws Exception {
550         VThreadRunner.run(() -> {
551             try (var listener = new ServerSocket()) {
552                 InetAddress loopback = InetAddress.getLoopbackAddress();
553                 listener.bind(new InetSocketAddress(loopback, 0));
554 
555                 // delayed interrupt of current thread
556                 Thread thisThread = Thread.currentThread();
557                 runAfterParkedAsync(thisThread::interrupt);
558 
559                 // accept should block, then throw
560                 if (timeout > 0) {
561                     listener.setSoTimeout(timeout);
562                 }
563                 try {
564                     listener.accept().close();
565                     fail("connection accepted???");
566                 } catch (SocketException expected) {
567                     assertTrue(Thread.interrupted());
568                     assertTrue(listener.isClosed());
569                 }
570             }
571         });
572     }
573 
574     /**
575      * DatagramSocket receive/send, no blocking.
576      */
577     @Test
578     void testDatagramSocketSendReceive1() throws Exception {
579         VThreadRunner.run(() -> {
580             try (DatagramSocket s1 = new DatagramSocket(null);
581                  DatagramSocket s2 = new DatagramSocket(null)) {
582 
583                 InetAddress lh = InetAddress.getLoopbackAddress();
584                 s1.bind(new InetSocketAddress(lh, 0));
585                 s2.bind(new InetSocketAddress(lh, 0));
586 
587                 // send should not block
588                 byte[] bytes = "XXX".getBytes("UTF-8");
589                 DatagramPacket p1 = new DatagramPacket(bytes, bytes.length);
590                 p1.setSocketAddress(s2.getLocalSocketAddress());
591                 s1.send(p1);
592 
593                 // receive should not block
594                 byte[] ba = new byte[100];
595                 DatagramPacket p2 = new DatagramPacket(ba, ba.length);
596                 s2.receive(p2);
597                 assertEquals(s1.getLocalSocketAddress(), p2.getSocketAddress());
598                 assertTrue(ba[0] == 'X');
599             }
600         });
601     }
602 
603     /**
604      * Virtual thread blocks in DatagramSocket receive.
605      */
606     @Test
607     void testDatagramSocketSendReceive2() throws Exception {
608         testDatagramSocketSendReceive(0);
609     }
610 
611     /**
612      * Virtual thread blocks in DatagramSocket receive with timeout.
613      */
614     @Test
615     void testDatagramSocketSendReceive3() throws Exception {
616         testDatagramSocketSendReceive(60_000);
617     }
618 
619     private void testDatagramSocketSendReceive(int timeout) throws Exception {
620         VThreadRunner.run(() -> {
621             try (DatagramSocket s1 = new DatagramSocket(null);
622                  DatagramSocket s2 = new DatagramSocket(null)) {
623 
624                 InetAddress lh = InetAddress.getLoopbackAddress();
625                 s1.bind(new InetSocketAddress(lh, 0));
626                 s2.bind(new InetSocketAddress(lh, 0));
627 
628                 // delayed send
629                 byte[] bytes = "XXX".getBytes("UTF-8");
630                 DatagramPacket p1 = new DatagramPacket(bytes, bytes.length);
631                 p1.setSocketAddress(s2.getLocalSocketAddress());
632                 runAfterParkedAsync(() -> s1.send(p1));
633 
634                 // receive should block
635                 if (timeout > 0) {
636                     s2.setSoTimeout(timeout);
637                 }
638                 byte[] ba = new byte[100];
639                 DatagramPacket p2 = new DatagramPacket(ba, ba.length);
640                 s2.receive(p2);
641                 assertEquals(s1.getLocalSocketAddress(), p2.getSocketAddress());
642                 assertTrue(ba[0] == 'X');
643             }
644         });
645     }
646 
647     /**
648      * Virtual thread blocks in DatagramSocket receive that times out.
649      */
650     @Test
651     void testDatagramSocketReceiveTimeout() throws Exception {
652         VThreadRunner.run(() -> {
653             try (DatagramSocket s = new DatagramSocket(null)) {
654                 InetAddress lh = InetAddress.getLoopbackAddress();
655                 s.bind(new InetSocketAddress(lh, 0));
656                 s.setSoTimeout(500);
657                 byte[] ba = new byte[100];
658                 DatagramPacket p = new DatagramPacket(ba, ba.length);
659                 try {
660                     s.receive(p);
661                     fail();
662                 } catch (SocketTimeoutException expected) { }
663             }
664         });
665     }
666 
667     /**
668      * DatagramSocket close while virtual thread blocked in receive.
669      */
670     @Test
671     void testDatagramSocketReceiveAsyncClose1() throws Exception {
672         testDatagramSocketReceiveAsyncClose(0);
673     }
674 
675     /**
676      * DatagramSocket close while virtual thread blocked with timeout.
677      */
678     @Test
679     void testDatagramSocketReceiveAsyncClose2() throws Exception {
680         testDatagramSocketReceiveAsyncClose(60_000);
681     }
682 
683     private void testDatagramSocketReceiveAsyncClose(int timeout) throws Exception {
684         VThreadRunner.run(() -> {
685             try (DatagramSocket s = new DatagramSocket(null)) {
686                 InetAddress lh = InetAddress.getLoopbackAddress();
687                 s.bind(new InetSocketAddress(lh, 0));
688 
689                 // delayed close of s
690                 runAfterParkedAsync(s::close);
691 
692                 // receive should block, then throw
693                 if (timeout > 0) {
694                     s.setSoTimeout(timeout);
695                 }
696                 try {
697                     byte[] ba = new byte[100];
698                     DatagramPacket p = new DatagramPacket(ba, ba.length);
699                     s.receive(p);
700                     fail();
701                 } catch (SocketException expected) { }
702             }
703         });
704     }
705 
706     /**
707      * Virtual thread interrupted while blocked in DatagramSocket receive.
708      */
709     @Test
710     void testDatagramSocketReceiveInterrupt1() throws Exception {
711         testDatagramSocketReceiveInterrupt(0);
712     }
713 
714     /**
715      * Virtual thread interrupted while blocked in DatagramSocket receive with timeout.
716      */
717     @Test
718     void testDatagramSocketReceiveInterrupt2() throws Exception {
719         testDatagramSocketReceiveInterrupt(60_000);
720     }
721 
722     private void testDatagramSocketReceiveInterrupt(int timeout) throws Exception {
723         VThreadRunner.run(() -> {
724             try (DatagramSocket s = new DatagramSocket(null)) {
725                 InetAddress lh = InetAddress.getLoopbackAddress();
726                 s.bind(new InetSocketAddress(lh, 0));
727 
728                 // delayed interrupt of current thread
729                 Thread thisThread = Thread.currentThread();
730                 runAfterParkedAsync(thisThread::interrupt);
731 
732                 // receive should block, then throw
733                 if (timeout > 0) {
734                     s.setSoTimeout(timeout);
735                 }
736                 try {
737                     byte[] ba = new byte[100];
738                     DatagramPacket p = new DatagramPacket(ba, ba.length);
739                     s.receive(p);
740                     fail();
741                 } catch (SocketException expected) {
742                     assertTrue(Thread.interrupted());
743                     assertTrue(s.isClosed());
744                 }
745             }
746         });
747     }
748 
749     /**
750      * Creates a loopback connection
751      */
752     static class Connection implements Closeable {
753         private final Socket s1;
754         private final Socket s2;
755         Connection() throws IOException {
756             var lh = InetAddress.getLoopbackAddress();
757             try (var listener = new ServerSocket()) {
758                 listener.bind(new InetSocketAddress(lh, 0));
759                 Socket s1 = new Socket();
760                 Socket s2;
761                 try {
762                     s1.connect(listener.getLocalSocketAddress());
763                     s2 = listener.accept();
764                 } catch (IOException ioe) {
765                     s1.close();
766                     throw ioe;
767                 }
768                 this.s1 = s1;
769                 this.s2 = s2;
770             }
771 
772         }
773         Socket socket1() {
774             return s1;
775         }
776         Socket socket2() {
777             return s2;
778         }
779         @Override
780         public void close() throws IOException {
781             s1.close();
782             s2.close();
783         }
784     }
785 
786     @FunctionalInterface
787     interface ThrowingRunnable {
788         void run() throws Exception;
789     }
790 
791     /**
792      * Runs the given task asynchronously after the current virtual thread has parked.
793      * @return the thread started to run the task
794      */
795     static Thread runAfterParkedAsync(ThrowingRunnable task) {
796         Thread target = Thread.currentThread();
797         if (!target.isVirtual())
798             throw new WrongThreadException();
799         return Thread.ofPlatform().daemon().start(() -> {
800             try {
801                 Thread.State state = target.getState();
802                 while (state != Thread.State.WAITING
803                         && state != Thread.State.TIMED_WAITING) {
804                     Thread.sleep(20);
805                     state = target.getState();
806                 }
807                 Thread.sleep(20);  // give a bit more time to release carrier
808                 task.run();
809             } catch (Exception e) {
810                 e.printStackTrace();
811             }
812         });
813     }
814 }