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 }