1313import java .net .http .HttpClient ;
1414import java .net .http .HttpRequest ;
1515import java .net .http .HttpResponse ;
16+ import java .time .Duration ;
1617import java .util .concurrent .*;
1718import java .util .concurrent .atomic .AtomicBoolean ;
1819import java .util .concurrent .atomic .AtomicInteger ;
20+ import java .util .concurrent .atomic .AtomicReference ;
1921
2022import io .netty .channel .Channel ;
2123import io .netty .channel .ChannelInboundHandlerAdapter ;
@@ -494,14 +496,16 @@ void testBlockingIO() throws IOException, InterruptedException, ExecutionExcepti
494496 var clientOut = clientSocket .getOutputStream ();
495497 var serverIn = serverSocket .getInputStream ()) {
496498 var readerVThreadPromise = new CompletableFuture <Thread >();
499+ var readCompleted = new CompletableFuture <Void >();
497500 group .execute (() -> {
498501 var readerThread = group .vThreadFactory ().newThread (() -> {
499502 var eventLoopScheduler = EventLoopScheduler .currentThreadSchedulerContext ().scheduler ().get ();
500503 assertEquals (0 , eventLoopScheduler .externalContinuationsCount ());
501504 try {
502505 serverIn .read ();
503- } catch (IOException e ) {
504- throw new RuntimeException (e );
506+ readCompleted .complete (null );
507+ } catch (Throwable e ) {
508+ readCompleted .completeExceptionally (e );
505509 }
506510 });
507511 readerVThreadPromise .complete (readerThread );
@@ -518,4 +522,133 @@ void testBlockingIO() throws IOException, InterruptedException, ExecutionExcepti
518522 }
519523 }
520524 }
525+
526+ @ Test
527+ void testShutdownSchedulerOnBlockingIO () throws IOException , InterruptedException , ExecutionException {
528+ assumeTrue (NettyScheduler .perCarrierPollers ());
529+ try (var group = new VirtualMultithreadIoEventLoopGroup (1 , LocalIoHandler .newFactory ());
530+ var serverAcceptor = new ServerSocket (0 )) {
531+ var serverSocketPromise = new CompletableFuture <Socket >();
532+ Thread .ofVirtual ().start (() -> {
533+ try {
534+ serverSocketPromise .complete (serverAcceptor .accept ());
535+ } catch (Throwable e ) {
536+ // complete exceptionally
537+ serverSocketPromise .completeExceptionally (e );
538+ }
539+ });
540+ try (var clientSocket = new Socket ("localhost" , serverAcceptor .getLocalPort ());
541+ var serverSocket = serverSocketPromise .join ();
542+ var clientOut = clientSocket .getOutputStream ();
543+ var serverIn = serverSocket .getInputStream ()) {
544+ var schedulerRef = new AtomicReference <EventLoopScheduler .SharedRef >();
545+ var firstReadCompleted = new CompletableFuture <Void >();
546+ var secondReadCompleted = new CompletableFuture <Void >();
547+ var readerVThreadPromise = new CompletableFuture <Thread >();
548+ group .execute (() -> {
549+ var readerThread = group .vThreadFactory ().newThread (() -> {
550+ schedulerRef .lazySet (EventLoopScheduler .currentThreadSchedulerContext ().scheduler ());
551+ var eventLoopScheduler = EventLoopScheduler .currentThreadSchedulerContext ().scheduler ().get ();
552+ assertEquals (0 , eventLoopScheduler .externalContinuationsCount ());
553+ try {
554+ serverIn .read ();
555+ firstReadCompleted .complete (null );
556+ try {
557+ serverIn .read ();
558+ secondReadCompleted .complete (null );
559+ } catch (Throwable e ) {
560+ secondReadCompleted .completeExceptionally (e );
561+ }
562+ } catch (Throwable e ) {
563+ firstReadCompleted .completeExceptionally (e );
564+ }
565+ });
566+ readerVThreadPromise .complete (readerThread );
567+ readerThread .start ();
568+
569+ });
570+ var readerVThread = readerVThreadPromise .get ();
571+ // it has to be waiting on read
572+ while (readerVThread .getState () != Thread .State .WAITING ) {
573+ Thread .sleep (1 );
574+ }
575+ Thread carrier = schedulerRef .get ().get ().carrierThread ();
576+ group .shutdownGracefully ().get ();
577+ assertTrue (carrier .join (Duration .MAX ));
578+ // unblock the client and expect the read to complete
579+ clientOut .write (1 );
580+ firstReadCompleted .join ();
581+ // it has to be waiting on read
582+ while (readerVThread .getState () != Thread .State .WAITING ) {
583+ Thread .sleep (1 );
584+ }
585+ clientOut .write (1 );
586+ secondReadCompleted .join ();
587+ }
588+ }
589+ }
590+
591+ @ Test
592+ void testShutdownSchedulerOnLongBlockingIO () throws IOException , InterruptedException , ExecutionException {
593+ assumeTrue (NettyScheduler .perCarrierPollers ());
594+ int bytesToWrite = 16 ;
595+ try (var group = new VirtualMultithreadIoEventLoopGroup (1 , LocalIoHandler .newFactory ());
596+ var serverAcceptor = new ServerSocket (0 )) {
597+ var serverSocketPromise = new CompletableFuture <Socket >();
598+ Thread .ofVirtual ().start (() -> {
599+ try {
600+ serverSocketPromise .complete (serverAcceptor .accept ());
601+ } catch (Throwable e ) {
602+ // complete exceptionally
603+ serverSocketPromise .completeExceptionally (e );
604+ }
605+ });
606+ try (var clientSocket = new Socket ("localhost" , serverAcceptor .getLocalPort ());
607+ var serverSocket = serverSocketPromise .join ();
608+ var clientOut = clientSocket .getOutputStream ();
609+ var serverIn = serverSocket .getInputStream ()) {
610+ var schedulerRef = new AtomicReference <EventLoopScheduler .SharedRef >();
611+ var readCompleted = new CompletableFuture <byte []>();;
612+ var readerVThreadPromise = new CompletableFuture <Thread >();
613+ group .execute (() -> {
614+ var readerThread = group .vThreadFactory ().newThread (() -> {
615+ schedulerRef .lazySet (EventLoopScheduler .currentThreadSchedulerContext ().scheduler ());
616+ var eventLoopScheduler = EventLoopScheduler .currentThreadSchedulerContext ().scheduler ().get ();
617+ assertEquals (0 , eventLoopScheduler .externalContinuationsCount ());
618+ try {
619+ byte [] data = serverIn .readNBytes (bytesToWrite );
620+ readCompleted .complete (data );
621+ } catch (Throwable e ) {
622+ readCompleted .completeExceptionally (e );
623+ }
624+ });
625+ readerVThreadPromise .complete (readerThread );
626+ readerThread .start ();
627+
628+ });
629+ var readerVThread = readerVThreadPromise .get ();
630+ byte [] toWrite = new byte [bytesToWrite ];
631+ int shutDownAt = toWrite .length / 2 ;
632+ for (int i = 0 ; i < toWrite .length ; i ++) {
633+ toWrite [i ] = (byte ) i ;
634+ }
635+ // it has to be waiting on read
636+ while (readerVThread .getState () != Thread .State .WAITING ) {
637+ Thread .sleep (1 );
638+ }
639+ for (int i = 0 ; i < toWrite .length ; i ++) {
640+ Thread .sleep (10 ); // make sure the read is parked
641+ byte b = toWrite [i ];
642+ // shutdown whilst the read is parked
643+ if (i == shutDownAt ) {
644+ Thread carrier = schedulerRef .get ().get ().carrierThread ();
645+ group .shutdownGracefully ().get ();
646+ assertTrue (carrier .join (Duration .MAX ));
647+ }
648+ clientOut .write (b );
649+ }
650+ assertArrayEquals (toWrite , readCompleted .join ());
651+ }
652+ }
653+ }
521654}
0 commit comments