You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ae4f4bf
Browse filesBrowse the repository at this point in the historyBrowse files
binder: Deliver closed() to streams started after shutdown
BinderClientTransport.newStream() registered a new ClientInbound in
ongoingCalls right away but the Inbound only learned about its
ClientStreamListener later, in ClientStream.start(). If the transport
terminated in between (binderDied(), onUnbound(), a peer's
SHUTDOWN_TRANSPORT or shutdownNow(), all of which close ongoing calls
asynchronously), closeAbnormal() found an UNINITIALIZED Inbound with no
listener or StatsTraceContext to report to. start() then attached the
listener to an Inbound that was already closed and the RPC hung forever
with its tracers never closed.
Registering at newStream() was also a leak waiting to happen: gRPC core
can abandon a ClientStream it got from newStream() without ever calling
start() or cancel() on it (DelayedStream.setStream() silently drops the
real stream if the DelayedStream was cancelled in the meantime). Such a
stream stayed in ongoingCalls and counted as in-use forever, so a
graceful shutdown() of that transport could never terminate.
Make newStream() do allocations only and move registration into
start(), as OkHttp and Netty do. The new BinderClientTransport
.startStream() helper holds the transport lock across both registration
and Inbound.init() so, relative to shutdownInternal()'s snapshot of
ongoingCalls, there are only two possible outcomes: either shutdown ran
first, in which case start() reports closed(shutdownStatus, MISCARRIED)
without registering anything (mirroring OkHttp's streamReadyToStart()
and allowing a transparent retry on a fresh transport), or we
registered an Inbound that already has a listener for the eventual
close to be delivered to. The listener callback itself runs with no
locks held. The miscarried stream's Outbound is put into CLOSED so that
the writeMessage()/halfClose() ClientCalls issues right after start()
can't send anything for a call the server must never see.
Two behaviour changes follow, both consistent with OkHttp/Netty:
- transportInUse(true) is reported at start() rather than newStream().
- A graceful shutdown() between newStream() and start() no longer waits
for the unstarted stream, whose start() then miscarries.
Since nothing can reach an Inbound that was never registered, the only
way a client Inbound can still be UNINITIALIZED when cancel() runs is
if cancel() came before start(). ClientStream#cancel says that's not
allowed, gRPC core's paths honour it (ClientCallImpl arms Context and
deadline cancellation only after start(), see issue #1343) and other
transports NPE if it happens, so cancel() now throws
IllegalStateException instead of silently closing a stream nobody is
listening to. A cancel() after a miscarried start() remains a legal
no-op. unregisterInbound() only decrements the in-use count for an
Inbound that was actually registered.
0 commit comments