@@ -123,11 +123,6 @@ interface UnderlyingCall {
123123 state : UnderlyingCallState ;
124124 call : LoadBalancingCall ;
125125 nextMessageToSend : number ;
126- /**
127- * Tracks the highest message index that has been sent to the underlying call.
128- * This is different from nextMessageToSend which tracks completion/acknowledgment.
129- */
130- highestSentMessageIndex : number ;
131126 startTime : Date ;
132127}
133128
@@ -700,7 +695,6 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
700695 state : 'ACTIVE' ,
701696 call : child ,
702697 nextMessageToSend : 0 ,
703- highestSentMessageIndex : - 1 ,
704698 startTime : new Date ( ) ,
705699 } ) ;
706700 const previousAttempts = this . attempts - 1 ;
@@ -793,7 +787,6 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
793787 } ,
794788 bufferEntry . message ! . message
795789 ) ;
796- childCall . highestSentMessageIndex = messageIndex ;
797790 // Optimization: if the next entry is HALF_CLOSE, send it immediately
798791 // without waiting for the message callback. This is safe because the message
799792 // has already been passed to the underlying transport.
@@ -833,7 +826,11 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
833826 } ;
834827 this . writeBuffer . push ( bufferEntry ) ;
835828 if ( bufferEntry . allocated ) {
836- context . callback ?.( ) ;
829+ // Run this in next tick to avoid suspending the current execution context
830+ // otherwise it might cause half closing the call before sending message
831+ process . nextTick ( ( ) => {
832+ context . callback ?.( ) ;
833+ } ) ;
837834 for ( const [ callIndex , call ] of this . underlyingCalls . entries ( ) ) {
838835 if (
839836 call . state === 'ACTIVE' &&
@@ -848,7 +845,6 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
848845 } ,
849846 message
850847 ) ;
851- call . highestSentMessageIndex = messageIndex ;
852848 }
853849 }
854850 } else {
@@ -860,7 +856,6 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
860856 const call = this . underlyingCalls [ this . committedCallIndex ] ;
861857 bufferEntry . callback = context . callback ;
862858 if ( call . state === 'ACTIVE' && call . nextMessageToSend === messageIndex ) {
863- call . highestSentMessageIndex = messageIndex ;
864859 call . call . sendMessageWithContext (
865860 {
866861 callback : error => {
@@ -891,11 +886,11 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
891886 } ) ;
892887 for ( const call of this . underlyingCalls ) {
893888 if ( call ?. state === 'ACTIVE' ) {
894- // Send halfClose immediately if all messages have been sent to this call
895- // We check highestSentMessageIndex >= halfCloseIndex - 1 because:
896- // - If halfCloseIndex is N, the last message is at index N-1
897- // - If highestSentMessageIndex >= N-1, all messages have been sent
898- if ( call . highestSentMessageIndex > = halfCloseIndex - 1 ) {
889+ // Send halfClose to call when either:
890+ // - nextMessageToSend === halfCloseIndex - 1: last message sent, callback pending (optimization)
891+ // - nextMessageToSend === halfCloseIndex: all messages sent and acknowledged
892+ if ( call . nextMessageToSend === halfCloseIndex
893+ || call . nextMessageToSend == = halfCloseIndex - 1 ) {
899894 this . trace (
900895 'Sending halfClose immediately to child [' +
901896 call . call . getCallNumber ( ) +
@@ -904,7 +899,7 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
904899 call . nextMessageToSend += 1 ;
905900 call . call . halfClose ( ) ;
906901 }
907- // Otherwise, halfClose will be sent by sendNextChildMessage when messages complete
902+ // Otherwise, halfClose will be sent by sendNextChildMessage when message callbacks complete
908903 }
909904 }
910905 }
0 commit comments