83
83
import java .util .Locale ;
84
84
import java .util .Map ;
85
85
import java .util .Random ;
86
+ import java .util .concurrent .BrokenBarrierException ;
86
87
import java .util .concurrent .CountDownLatch ;
88
+ import java .util .concurrent .CyclicBarrier ;
87
89
import java .util .concurrent .Executor ;
88
90
import java .util .concurrent .ScheduledExecutorService ;
91
+ import java .util .concurrent .TimeUnit ;
92
+ import java .util .concurrent .TimeoutException ;
89
93
import java .util .logging .Level ;
90
94
import java .util .logging .Logger ;
91
95
import javax .annotation .Nullable ;
@@ -499,8 +503,15 @@ public Runnable start(Listener listener) {
499
503
outboundFlow = new OutboundFlowController (this , frameWriter );
500
504
}
501
505
final CountDownLatch latch = new CountDownLatch (1 );
506
+ final CountDownLatch latchForExtraThread = new CountDownLatch (1 );
507
+ // The transport needs up to two threads to function once started,
508
+ // but only needs one during handshaking. Start another thread during handshaking
509
+ // to make sure there's still a free thread available. If the number of threads is exhausted,
510
+ // it is better to kill the transport than for all the transports to hang unable to send.
511
+ CyclicBarrier barrier = new CyclicBarrier (2 );
502
512
// Connecting in the serializingExecutor, so that some stream operations like synStream
503
513
// will be executed after connected.
514
+
504
515
serializingExecutor .execute (new Runnable () {
505
516
@ Override
506
517
public void run () {
@@ -510,8 +521,14 @@ public void run() {
510
521
// initial preface.
511
522
try {
512
523
latch .await ();
524
+ barrier .await (1000 , TimeUnit .MILLISECONDS );
513
525
} catch (InterruptedException e ) {
514
526
Thread .currentThread ().interrupt ();
527
+ } catch (TimeoutException | BrokenBarrierException e ) {
528
+ startGoAway (0 , ErrorCode .INTERNAL_ERROR , Status .UNAVAILABLE
529
+ .withDescription ("Timed out waiting for second handshake thread. "
530
+ + "The transport executor pool may have run out of threads" ));
531
+ return ;
515
532
}
516
533
// Use closed source on failure so that the reader immediately shuts down.
517
534
BufferedSource source = Okio .buffer (new Source () {
@@ -575,6 +592,7 @@ sslSocketFactory, hostnameVerifier, sock, getOverridenHost(), getOverridenPort()
575
592
return ;
576
593
} finally {
577
594
clientFrameHandler = new ClientFrameHandler (variant .newReader (source , true ));
595
+ latchForExtraThread .countDown ();
578
596
}
579
597
synchronized (lock ) {
580
598
socket = Preconditions .checkNotNull (sock , "socket" );
@@ -584,6 +602,21 @@ sslSocketFactory, hostnameVerifier, sock, getOverridenHost(), getOverridenPort()
584
602
}
585
603
}
586
604
});
605
+
606
+ executor .execute (new Runnable () {
607
+ @ Override
608
+ public void run () {
609
+ try {
610
+ barrier .await (1000 , TimeUnit .MILLISECONDS );
611
+ latchForExtraThread .await ();
612
+ } catch (BrokenBarrierException | TimeoutException e ) {
613
+ // Something bad happened, maybe too few threads available!
614
+ // This will be handled in the handshake thread.
615
+ } catch (InterruptedException e ) {
616
+ Thread .currentThread ().interrupt ();
617
+ }
618
+ }
619
+ });
587
620
// Schedule to send connection preface & settings before any other write.
588
621
try {
589
622
sendConnectionPrefaceAndSettings ();
0 commit comments