2020import com .google .common .util .concurrent .ListenableFuture ;
2121import com .google .common .util .concurrent .SettableFuture ;
2222import com .google .errorprone .annotations .concurrent .GuardedBy ;
23+ import io .grpc .Attributes .Builder ;
2324import io .grpc .CallOptions ;
2425import io .grpc .ClientStreamTracer ;
2526import io .grpc .Context ;
2627import io .grpc .InternalChannelz .SocketStats ;
2728import io .grpc .InternalLogId ;
2829import io .grpc .LoadBalancer .PickResult ;
2930import io .grpc .LoadBalancer .PickSubchannelArgs ;
31+ import io .grpc .LoadBalancer .Subchannel ;
3032import io .grpc .LoadBalancer .SubchannelPicker ;
3133import io .grpc .Metadata ;
3234import io .grpc .MethodDescriptor ;
3335import io .grpc .Status ;
3436import io .grpc .SynchronizationContext ;
3537import io .grpc .internal .ClientStreamListener .RpcProgress ;
38+ import io .grpc .internal .InternalSubchannel .SubchannelStatusListener ;
3639import java .util .ArrayList ;
3740import java .util .Collection ;
3841import java .util .Collections ;
@@ -129,6 +132,7 @@ public final ClientStream newStream(
129132 if (state .shutdownStatus != null ) {
130133 return new FailingClientStream (state .shutdownStatus , tracers );
131134 }
135+ InternalSubchannel internalSubchannel = null ;
132136 if (state .lastPicker != null ) {
133137 PickResult pickResult = state .lastPicker .pickSubchannel (args );
134138 callOptions = args .getCallOptions ();
@@ -149,14 +153,21 @@ public final ClientStream newStream(
149153 stream .setAuthority (pickResult .getAuthorityOverride ());
150154 }
151155 return stream ;
156+ } else {
157+ if (args .getCallOptions ().isWaitForReady ()) {
158+ Subchannel subchannel = pickResult .getSubchannel ();
159+ if (subchannel != null ) {
160+ internalSubchannel = (InternalSubchannel ) subchannel .getInternalSubchannel ();
161+ }
162+ }
152163 }
153164 }
154165 // This picker's conclusion is "buffer". If there hasn't been a newer picker set (possible
155166 // race with reprocess()), we will buffer the RPC. Otherwise, will try with the new picker.
156167 synchronized (lock ) {
157168 PickerState newerState = pickerState ;
158169 if (state == newerState ) {
159- return createPendingStream (args , tracers );
170+ return createPendingStream (args , tracers , internalSubchannel );
160171 }
161172 state = newerState ;
162173 }
@@ -172,8 +183,8 @@ public final ClientStream newStream(
172183 */
173184 @ GuardedBy ("lock" )
174185 private PendingStream createPendingStream (
175- PickSubchannelArgs args , ClientStreamTracer [] tracers ) {
176- PendingStream pendingStream = new PendingStream (args , tracers );
186+ PickSubchannelArgs args , ClientStreamTracer [] tracers , InternalSubchannel internalSubchannel ) {
187+ PendingStream pendingStream = new PendingStream (args , tracers , internalSubchannel );
177188 pendingStreams .add (pendingStream );
178189 if (getPendingStreamsCount () == 1 ) {
179190 syncContext .executeLater (reportTransportInUse );
@@ -308,7 +319,13 @@ final void reprocess(@Nullable SubchannelPicker picker) {
308319 executor .execute (runnable );
309320 }
310321 toRemove .add (stream );
311- } // else: stay pending
322+ } else { // else: stay pending
323+ Subchannel subchannel = pickResult .getSubchannel ();
324+ if (subchannel != null ) {
325+ InternalSubchannel internalSubchannel = (InternalSubchannel ) subchannel .getInternalSubchannel ();
326+ internalSubchannel .addSubchannelStatusListener (stream );
327+ }
328+ }
312329 }
313330
314331 synchronized (lock ) {
@@ -345,14 +362,18 @@ public InternalLogId getLogId() {
345362 return logId ;
346363 }
347364
348- private class PendingStream extends DelayedStream {
365+ private class PendingStream extends DelayedStream implements SubchannelStatusListener {
349366 private final PickSubchannelArgs args ;
350367 private final Context context = Context .current ();
351368 private final ClientStreamTracer [] tracers ;
369+ private Status lastTransportStatus ;
352370
353- private PendingStream (PickSubchannelArgs args , ClientStreamTracer [] tracers ) {
371+ private PendingStream (PickSubchannelArgs args , ClientStreamTracer [] tracers , InternalSubchannel internalSubchannel ) {
354372 this .args = args ;
355373 this .tracers = tracers ;
374+ if (internalSubchannel != null ) {
375+ internalSubchannel .addSubchannelStatusListener (this );
376+ }
356377 }
357378
358379 /** Runnable may be null. */
@@ -406,8 +427,16 @@ public void appendTimeoutInsight(InsightBuilder insight) {
406427 if (args .getCallOptions ().isWaitForReady ()) {
407428 insight .append ("wait_for_ready" );
408429 }
430+ if (lastTransportStatus != null && !lastTransportStatus .isOk ()) {
431+ insight .appendKeyValue ("Last Transport Status" , lastTransportStatus );
432+ }
409433 super .appendTimeoutInsight (insight );
410434 }
435+
436+ @ Override
437+ public void updateState (Status s ) {
438+ this .lastTransportStatus = s ;
439+ }
411440 }
412441
413442 static final class PickerState {
0 commit comments