-
Notifications
You must be signed in to change notification settings - Fork 687
Send halfClose immediately after messages to prevent late halfClose issues with Envoy #3031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
68a9a6d
298e39a
1d546ee
699ca49
b62f609
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,6 +123,15 @@ interface UnderlyingCall { | |
| state: UnderlyingCallState; | ||
| call: LoadBalancingCall; | ||
| nextMessageToSend: number; | ||
| /** | ||
| * Tracks the highest message index that has been sent to the underlying call. | ||
| * This is different from nextMessageToSend which tracks completion/acknowledgment. | ||
| */ | ||
| highestSentMessageIndex: number; | ||
| /** | ||
| * Tracks whether halfClose has been sent to this child call. | ||
| */ | ||
| halfCloseSent: boolean; | ||
| startTime: Date; | ||
| } | ||
|
|
||
|
|
@@ -695,6 +704,8 @@ export class RetryingCall implements Call, DeadlineInfoProvider { | |
| state: 'ACTIVE', | ||
| call: child, | ||
| nextMessageToSend: 0, | ||
| highestSentMessageIndex: -1, | ||
| halfCloseSent: false, | ||
| startTime: new Date(), | ||
| }); | ||
| const previousAttempts = this.attempts - 1; | ||
|
|
@@ -778,6 +789,7 @@ export class RetryingCall implements Call, DeadlineInfoProvider { | |
| const bufferEntry = this.getBufferEntry(childCall.nextMessageToSend); | ||
| switch (bufferEntry.entryType) { | ||
| case 'MESSAGE': | ||
| childCall.highestSentMessageIndex = childCall.nextMessageToSend; | ||
| childCall.call.sendMessageWithContext( | ||
| { | ||
| callback: error => { | ||
|
|
@@ -787,10 +799,26 @@ export class RetryingCall implements Call, DeadlineInfoProvider { | |
| }, | ||
| bufferEntry.message!.message | ||
| ); | ||
| // Optimization: if the next entry is HALF_CLOSE, send it immediately | ||
| // without waiting for the message callback. This is safe because the message | ||
| // has already been passed to the underlying transport. | ||
| const nextEntry = this.getBufferEntry(childCall.nextMessageToSend + 1); | ||
| if (nextEntry.entryType === 'HALF_CLOSE' && !childCall.halfCloseSent) { | ||
| this.trace( | ||
| 'Sending halfClose immediately after message to child [' + | ||
| childCall.call.getCallNumber() + | ||
| '] - optimizing for unary/final message' | ||
| ); | ||
| childCall.halfCloseSent = true; | ||
| childCall.call.halfClose(); | ||
| } | ||
| break; | ||
| case 'HALF_CLOSE': | ||
| childCall.nextMessageToSend += 1; | ||
| childCall.call.halfClose(); | ||
| if (!childCall.halfCloseSent) { | ||
| childCall.nextMessageToSend += 1; | ||
| childCall.halfCloseSent = true; | ||
| childCall.call.halfClose(); | ||
| } | ||
| break; | ||
| case 'FREED': | ||
| // Should not be possible | ||
|
|
@@ -819,6 +847,7 @@ export class RetryingCall implements Call, DeadlineInfoProvider { | |
| call.state === 'ACTIVE' && | ||
| call.nextMessageToSend === messageIndex | ||
| ) { | ||
| call.highestSentMessageIndex = messageIndex; | ||
| call.call.sendMessageWithContext( | ||
| { | ||
| callback: error => { | ||
|
|
@@ -839,6 +868,7 @@ export class RetryingCall implements Call, DeadlineInfoProvider { | |
| const call = this.underlyingCalls[this.committedCallIndex]; | ||
| bufferEntry.callback = context.callback; | ||
| if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { | ||
| call.highestSentMessageIndex = messageIndex; | ||
| call.call.sendMessageWithContext( | ||
| { | ||
| callback: error => { | ||
|
|
@@ -868,12 +898,22 @@ export class RetryingCall implements Call, DeadlineInfoProvider { | |
| allocated: false, | ||
| }); | ||
| for (const call of this.underlyingCalls) { | ||
| if ( | ||
| call?.state === 'ACTIVE' && | ||
| call.nextMessageToSend === halfCloseIndex | ||
| ) { | ||
| call.nextMessageToSend += 1; | ||
| call.call.halfClose(); | ||
| if (call?.state === 'ACTIVE' && !call.halfCloseSent) { | ||
| // Send halfClose immediately if all messages have been sent to this call | ||
| // We check highestSentMessageIndex >= halfCloseIndex - 1 because: | ||
| // - If halfCloseIndex is 0, there are no messages, so send immediately | ||
| // - If halfCloseIndex is N, the last message is at index N-1 | ||
| // - If highestSentMessageIndex >= N-1, all messages have been sent | ||
| if (halfCloseIndex === 0 || call.highestSentMessageIndex >= halfCloseIndex - 1) { | ||
|
||
| this.trace( | ||
| 'Sending halfClose immediately to child [' + | ||
| call.call.getCallNumber() + | ||
| '] - all messages already sent' | ||
| ); | ||
| call.halfCloseSent = true; | ||
| call.call.halfClose(); | ||
| } | ||
| // Otherwise, halfClose will be sent by sendNextChildMessage when messages complete | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -895,4 +935,4 @@ export class RetryingCall implements Call, DeadlineInfoProvider { | |
| return null; | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think either of these fields is necessary.
nextMessageToSendshould be enough to track all of the relevant state.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we half close + increment nextMessageTosend, this line would't not work so callback of the message would not be executed, this is why it's added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding a new field, just make the message index a second argument to
handleChildWriteCompleted. InsendNextChildMessage, be careful to capturechildCall.nextMessageToSendin a message index variable before potentially incrementing it when sending the immediate half close.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sent a commit for this feedback.