-
Notifications
You must be signed in to change notification settings - Fork 93
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
fix: handle stream has no metadata event #1132
Conversation
src/streamingCalls/streaming.ts
Outdated
stream.emit('response', { | ||
code: 200, | ||
details: '', | ||
message: 'OK', | ||
metadata: status.metadata, |
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.
@bcoe @alexander-fenster @JustinBeckwith Can I get the feedback on the object of response
event.
What I am doing here is try to consistent with response object see L156-L160, but seems like the status
received here has same structure, but different code. For example, status
receive using local Java emulator
{
code: 0,
details: '',
metadata: Metadata {
internalRepr: Map(2) { 'content-type' => [Array], 'trailer' => [Array] },
options: {}
}
}
In my opinion, we should return status as it is. Change status code to 200
cause confusion. But both status
and response
event emit same data. So I am not too sure from the user perspectively.
Any suggestion? Thank you .
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 think the code here is the grpc code not http:
https://grpc.github.io/grpc/core/md_doc_statuscodes.html
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.
Actually, nvm, I think I'm wrong:
https://screenshot.googleplex.com/GXJfqu8DKHq3DFF
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 would recommend not including status.metadata
here. It contains different information than what you would get from the metadata
event. I would also recommend otherwise matching the response
event emitted in the metadata
handler, instead of trying to use the information from the status
. I think it is not really correct to say that "both status
and response
event emit same data". They have similar structures, and the specific fields can match in some cases, but they're not really describing the same thing: response
seems to describe whether the response started successfully, while status
describes whether the response completed successfully.
And just to make sure it is clear, the code
in the status
event is a gRPC status code, and the code in the response
event is an HTTP status code. The HTTP status code is sent in the gRPC protocol, but it is not exposed in the gRPC API.
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.
It seems like remapping 0
-> 200
is reasonable, but should we support other grpc status codes and check the repsonse?
Would look to @murgatroid99's advice.
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.
Try to do the same thing in code, with removing the metadata.
stream.emit('response', {
code: 200,
details: '',
message: 'OK',
});
@@ -134,6 +159,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { | |||
message: 'OK', |
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.
Should this be also put inside if (!responseHasSent)
?
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 think it doesn't matter. The stream event sequence is metadata first, then status is the last event to be emit.
We only care when the status
received, we don't emit the response
twice.
@murgatroid99 WDYT
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 agree.
test/unit/streaming.ts
Outdated
@@ -257,6 +257,71 @@ describe('streaming', () => { | |||
}, 50); | |||
}); | |||
|
|||
it('Not receive metadata event', done => { |
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.
Do we have a test coverage to check the responseHasSent
logic? Probably add a counter to the test and check that the response event was emitted just once.
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.
Add two test cases to cover the responseHasSent
:
- Stream received both
metadata
andstatus
events.
response
should containsmetadata
field.
- Stream received only
status
events.
response
should has nometadata
field.
cbebcc1
to
fb713b5
Compare
// Emit the 'response' event if stream has no 'metadata' event. | ||
// This avoids stream swallow the other events, such as 'end'. | ||
stream.on('status', () => { | ||
if (!this._responseHasSent) { |
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.
Do we set _this.responseHasSent
as we start to write the response, or after the response has finished?
If we set it at the end, this could be a race condition. If we set it at the beginning, this seems like it will work well to me.
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 think it doesn't matter. The stream event sequence is metadata
first, then status
is the last event to be emit.
We only care when the status received, we don't emit the response twice.
}); | ||
s.push(null); | ||
setImmediate(() => { | ||
s.emit('metadata', responseMetadata); |
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'm curious if metadata happened to be a huge buffer, or if this was happening on a slow connectoin, is there a chance status
would emit before metadata
finished reading?
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.
According to @murgatroid99, the status
event is always sent after the last message.
I assume the metadata
to be emitted early before status
. However, if the status
sent earlier, then the response
emit from status
event.
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 there is a metadata
event, it is always emitted first. If there are any data
events, they are emitted after metadata
. The status
event is always emitted, and it is always after the others.
stream.on('status', () => { | ||
if (!this._responseHasSent) { | ||
stream.emit('response', { | ||
code: 200, |
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.
status
always indicates a 200
?
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.
Try to do the same thing in code
src/streamingCalls/streaming.ts
Outdated
stream.emit('response', { | ||
code: 200, | ||
details: '', | ||
message: 'OK', | ||
metadata: status.metadata, |
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.
It seems like remapping 0
-> 200
is reasonable, but should we support other grpc status codes and check the repsonse?
Would look to @murgatroid99's advice.
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.
Left a couple nits, and some questions.
Can you point me to which library is listening on the response
event?
Co-authored-by: Benjamin E. Coe <[email protected]>
According to the comment, |
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.
LGTM, thank you for the fix 👍
When a user call
BigTable
server-stream APIreadrows
, theend
event get stuck when the BigTable is empty. This can be reproduce on local Java emulator, not on production and Node.js emulator.The root cause is that
grpc
handle the proxymetadata
differently, nometadata
event emit for Java emulator. (https://github.com/grpc/grpc-node/blob/master/packages/grpc-js/src/call-stream.ts#L527)However,
gax
emit theresponse
event once the grpc stream received themetadata
, since themetadata
has never been received in Java emulator case, stream seems swallow theend
event.Fix: grpc guaranteed
status
event, so we emit the response when receivedstatus
event for nometadata
event.Sample code Java emulator
Please reach out @summer-ji-eng if you need more detailed and internal information.