question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unary callback executed multiple times on gRPC error

See original GitHub issue

I notice on HTTP/1.1 200 responses with a valid gRPC error that the callback is being executed three times. I believe it’s a result of having a non-OK status code, which leads to executing the callback twice on “status” events and once more on the “error” event in grpcwebclientbase.js.

I found this to be surprising behavior (I expected at most a single non-null error callback), and since the callback is a required parameter I either have to use a local variable to track whether a non-null error has been previously encountered to prevent triggering error logic multiple times or pass in no-op callback and use the returned ClientReadableStream object.

Example server code (Go):

func (s *server) Foo(context.Context, *foo.FooRequest) (*foo.FooResponse, error) {
	return nil, status.Error(codes.Unimplemented, "unimplemented")
}

Example client code (Typescript):

let client = new FooClient("http://localhost:8888");
let req = new FooRequest();
client.foo(req, null, function(err, resp) {
  console.log(err);
  console.log(resp);
  console.trace();
});

Console output:

{code: 12, message: "unimplemented"}
null
[ stack trace where "status" callback is triggered ]
{code: 12, message: "unimplemented"}
null
[ stack trace where "status" callback is triggered ]
{code: 12, message: "unimplemented"}
null
[ stack trace where "error" callback is triggered ]

I’m curious whether this is intended behavior or not (the double-status event in particular seems odd).

If not, I’ve got a few ideas:

  1. Make the callback an optional parameter
  2. Remove the callback entirely to support/force a consistent coding approach (i.e., same as streaming)
  3. Don’t execute the callback for status updates (i.e., only execute on “data” or “error”)
  4. Call the callback at most once on the first non-OK status/error response (whichever comes first)

Environment details:

  • protoc-gen-grpc-web-1.0.6-darwin-x86_64
  • protoc 3.7.1
  • --js_out=import_style=commonjs:$OUT
  • --grpc-web_out=import_style=typescript,mode=grpcweb:$OUT
  • “google-protobuf”: “^3.10.0-rc.1”
  • “grpc-web”: “^1.0.6”
  • Server using github.com/improbable-eng/grpc-web/go/grpcweb

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:5
  • Comments:7

github_iconTop GitHub Comments

6reactions
ascherkuscommented, Sep 12, 2019

I tested locally against HEAD (192fef8909dcbc0c9889c348ec092ecf657c62e8) and while the behavior has changed, I still feel it’s confusing as an end user.

Sample code:

let stream = client.foo(req, null, function() {});
stream.on("status", function(err) {
  console.log("status")
});
stream.on("error", function(err) {
  console.log("error")
});
stream.on("data", function(resp) {
  console.log("data")
});

1.0.6 behavior:

status
status
error

HEAD behavior:

error
status

We now get one less status callback and the order of error and status are swapped. Using a callback still results in it firing twice on errors as opposed to three times.

I also noticed that mixing/matching stream.on() with the callback results in “hijacking” the events and prevents the callback being fired (e.g., registering stream.on("status", ...) will mean the callback will not get called on status updates). This was also surprising to me.

While I’m unblocked for my own project (I pass in no-op callbacks and exclusively use stream.on("error", ...) and stream.on("data", ...)), this seems less than ideal for newcomers to grpc-web.

Do you have an opinion on whether this is by design? Happy to contribute code, tests, documentation to clarify!

4reactions
vsilecommented, Apr 30, 2020

On 01 May 2020 I steel have the issue.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Core concepts, architecture and lifecycle - gRPC
Unary RPCs where the client sends a single request to the server and gets a single response back, just like a normal function...
Read more >
gRPC future callback got exception:StatusRuntimeException
Client makes a unary request, and server sends out a stream of responses in the same thread. So, I fail to see how...
Read more >
Reliable gRPC services with deadlines and cancellation
It is possible that a gRPC call completes on one machine, but by the time the response has returned to the client the...
Read more >
gRPC: synchronous and asynchronous unary RPC in Java
Asynchronous client stub solves this problem by registering a callback. This callback is called, in a different thread, once the server sends to ......
Read more >
gRPC Python 1.46.2 documentation
interceptors – Zero or more objects of type UnaryUnaryClientInterceptor, ... This method is idempotent and may be called at any time.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found