Repository navigation
Replies: 2 comments
TriageVerdict: A reproducible bug in Is this a misuse of humus?No. The behaviour is humus's on both sides. The end user's part is what the docs tell them to do. Their subscription subscribes again on The strongest case for misuse:
Is it a feature request?No. The behaviour contradicts what the docs promise:
What does describe the behaviour is the default's table and the bullet under it, quoted above. Restoring what the docs promise, and correcting the table, the bullet and the test that describe the bug, isn't an API change. Is it reproducible?Yes. The tests are in Each pod of the dependency is a
A rollout with 5 server streams: the breaker opens. ledger-a and ledger-b serve, and the 5 subscriptions are on ledger-a. At +0.1s, ledger-a leaves the endpoints and gets SIGTERM. In run 1:
Unary calls stay rejected past
This corrects three things in the report:
Why
Two more ways in, which I reasoned from code and didn't reproduce:
#1180 is filed to fix it:
|
|
Fixed by #1187, merged as 3d2e52c. An open stream that ends with The triage's reproductions are in |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
What happens
A
grpcclientClient with five or more server streams open to one pod of a dependency opens its circuit breaker when that pod shuts down, even while the dependency's other pods are healthy. Every call the Client makes, unary or stream, is then rejected forBreaker.OpenFor, 10s by default.grpcserver'sShutdownends every stream withUnavailableandserver is shutting down, so that its client reconnects to another pod, as Streams at shutdown describes.grpcclientcounts a stream that opened once, when it ends, with theBreakerofClassify's answer. The defaultClassifycountsUnavailableas aFailure.A rollout of the dependency does this to every client with five streams open to a pod it replaces, once for each pod.
MaxConnectionAgeGracedoes the same. When it closes a connection that still has streams open, each one ends withUnavailable,closing transport due to: ... received prior goaway: code: NO_ERROR, debug data: "max_age", and counts as a failure.Both sides run on humus's defaults. Nothing failed: the server ended the streams on purpose, as the docs say it should.
Why
Shutdownends a stream witherrShuttingDown,Unavailable(streams.go#L17-L20).RecvMsgreturns an error, withClassify's answer for that error (breaker.go#L226-L252). Only a caller's cancellation is released without counting.ClassifyanswersUnavailablewithcircuit.Failure(classify.go#L70-L71), whatever the method and however long the stream was open.settings.go#L180-L185).How it was found
While prototyping the gRPC walkthrough, against main at d4eadda, under
go test -race, insidesynctest, on one humustest Host.grpcclientClient onDefaultSettings(), and served it on to the test. Each backend stream sent a message a second until its context ended.grpcserverApps,ledger-aandledger-b. The front's Host dialed the Client'sAddrto whichever of them was "in the endpoints", as a ClusterIP Service does.ClassifyShutdownended the streams. 4 of the 6 couldn't open again:circuit: call rejected: open for another 10sUnavailable, rejectedIgnoredfor the end of a stream withUnavailableandserver is shutting downWith a single backend whose options set
MaxConnectionAge: time.MinuteandMaxConnectionAgeGrace: 10 * time.Second, and six streams open, the breaker opened when the grace closed the connection, about 76s in.grpcclient's own tests open one stream at a time, andgrpcserver's tests have no client with a breaker, so neither reaches this.The docs
Unavailableas aFailure. Neither says that a server's shutdown, or its connection age, ends streams that way.Unavailable. AgrpcclientClient can do that only once its breaker lets it.Expected
On humus's defaults, a dependency's rollout or its connection age doesn't open its clients' breakers. A stream that a server ends on purpose says nothing about whether the dependency answers calls. Some ways to get there:
Unavailable, isIgnored, as the end of a stream its caller cancelled is. A stream that fails to open still counts. So does the next attempt to open one, which is what tells a dependency that is down.grpcserverends streams at shutdown with a status the defaultClassifyknows, such as one with a detail of humus's, and the default ignores it. That coversShutdown, but notMaxConnectionAgeGrace, whose close is grpc-go's.Classifythat ignores a stream ended this way.Whichever is chosen:
Shutdown, and across itsMaxConnectionAgeGrace;References
UnavailableAll reactions