binder: RuntimeException during outbound serialization leaks the call and never notifies the peer
Les mainteneurs répondent en général sous 1 jour
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 48/100
- Type d'issue
- Bug
- Clarté
- Clairement spécifiée
- Activité
- Active
- Domaine
- backend-api-design
Piste de recherche
Start with Outbound.sendInternal() and trace its callers in MultiMessageClientStream, SingleMessageClientStream, MultiMessageServerStream, and SingleMessageServerStream; inspect the listed start, half-close, headers, and close entry points. Compare their exception handling with writeMessage() and the core marshaller guards. Add coverage for the throwing-metadata reproductions on both client and server; done means failures close the call with non-OK status, release transport resources, and do not escape.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
What version of gRPC-Java are you using?
1.85.0-SNAPSHOT
What is your environment?
Android
What did you expect to see?
When serializing outbound metadata or a message throws an unchecked exception from a binder stream method, the stream should end deterministically, the same way it does when writeMessage() throws:
- the peer receives a close for that
callId(so it fails immediately instead of waiting out its deadline); - the local
ClientCall.Listener.onClose()/ServerCall.Listener.onCancel()runs with a non-OK status, and the application is not told the RPC completed successfully; - the call is removed from the transport's
ongoingCalls(and the in-use count decremented) so gracefulshutdown()can finish; - The unchecked exception does not escape to the caller.
ClientCallImpl.sendMessageInternal() and ServerCallImpl.sendMessageInternal() already catch RuntimeException from marshallers and cancel the stream; the four entry points below should behave the same.
What did you see instead?
grpc-binder serializes headers, messages and trailers lazily inside Outbound.sendInternal(). That method may call into user-supplied code (Parcelable.writeToParcel(), Metadata.BinaryStreamMarshaller.toStream(), InputStream.read()/close() on marshaller streams) but only catches IOException; every caller above it (Outbound.send(), the four *ClientStream/*ServerStream classes, Inbound.onTransportReady()) only catches StatusException. An unchecked exception escapes the transport entirely from four entry points that core does not guard:
| Entry point | Core caller | Guarded by core? |
|---|---|---|
MultiMessageClientStream.start() (writes request headers) |
ClientCallImpl.startInternal() → stream.start() |
No |
SingleMessageClientStream.halfClose() (writes headers + the single request + half-close) |
ClientCallImpl.halfCloseInternal() |
No |
MultiMessageServerStream.writeHeaders() |
ServerCallImpl.sendHeadersInternal() |
No |
SingleMessageServerStream.close() / MultiMessageServerStream.close() (writes headers, pending message, status + trailers) |
ServerCallImpl.closeInternal() |
No |
(For unary calls writeMessage() only stashes pendingSingleMessage; all serialization, including of the message, happens in halfClose()/close(), outside core's sendMessage() guard.)
Steps to reproduce the bug
Common fixture:
// A Parcelable whose writeToParcel() always throws.
static final class ThrowingParcelable implements Parcelable {
@Override public void writeToParcel(Parcel parcel, int flags) {
throw new IllegalStateException("writeToParcel() failed");
}
// describeContents()/CREATOR omitted
}
static final Metadata.Key<ThrowingParcelable> POISON_KEY =
ParcelableUtils.metadataKey("poison-bin", ThrowingParcelable.CREATOR);
Server side:
- Register a unary echo method (
ServerCalls.asyncUnaryCall) wrapped in aServerInterceptorwhoseSimpleForwardingServerCall.close()doestrailers.put(POISON_KEY, ne w ThrowingParcelable())before delegating, and a second interceptor that records whether the appListenergetsonComplete()oronCancel(). - From the client,
ClientCalls.futureUnaryCall(channel.newCall(METHOD, CallOptions.DEFAULT.withDeadlineAfter(2, SECONDS)), Empty.getDefaultInstance()).get(10, SECONDS). - Assert the status is not
DEADLINE_EXCEEDED→ fails (it isDEADLINE_EXCEEDED). - Assert the recorded server listener outcome is
onCancel→ fails (it isonComplete). server.shutdown(); assertTrue(server.awaitTermination(10, SECONDS))→ fails.
Stack of the escaping exception: ServerCalls$ServerCallStreamObserverImpl.onCompleted → ServerCallImpl.close → SingleMessageServerStream.close → ServerOutbound.sendSingleMessageAndClose → Outbound.send → Outbound.sendInternal → ServerOutbound.writeSuffix → MetadataHelper.writeMetadata → ParcelableInputStream.writeToParcel, surfacing in JumpToApplicationThreadServerStreamListener$HalfClosed.runInContext.
Client side:
- Complete one clean unary RPC on the channel first, so the binder transport is
READYand the nextstart()reaches the binder stream synchronously (otherwiseDelayedClientTransportdefers it to the app executor and the exception surfaces there instead). - Wrap the channel with a
ClientInterceptorwhoseSimpleForwardingClientCall.start()doesheaders.put(POISON_KEY, new ThrowingParcelable()), andnewCall()aBIDI_STREAMINGmethod withwithDeadlineAfter(2, SECONDS). call.start(listener, new Metadata()). Assert it does not throw → fails (IllegalStateException: writeToParcel() failed).- Assert
listener.onClose()is invoked within 10 s → fails (TimeoutException; the deadline never fires). channel.shutdown(); assertTrue(channel.awaitTermination(10, SECONDS))→ fails.
- Langage dominant
- Java
- Étoiles
- 12.1k
- Forks
- 4k
- Merge moyen
- 1 j 23 h
- PR mergées (30 j)
- 44
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Aucun modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de grpc/grpc-java
-
Support of `dns:name` URIsOuvertedocs enhancement
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
grpc/grpc-java#10824 · 8 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
xds: CdsLoadBalancer2 keeps stale READY picker after EDS update with no usable endpointsPeut-être pris @lujiajing1126 l’a pris aujourd’hui. Ouverte
Difficulté 3/5 1-2 jours Accessibilité débutants 64/100
Les mainteneurs répondent en général sous 1 jour
-
question
Difficulté 4/5 3-5 jours Accessibilité débutants 25/100
grpc/grpc-java#13129 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
binder: `ServerStream.close()` marks the stream closed before the suffix is sent, so a flow-control-deferred send failure leaks the callPeut-être pris @kkmurthyt21 l’a pris il y a 13 jours. Ouvertebug
Difficulté 4/5 3-5 jours Accessibilité débutants 45/100
grpc/grpc-java#13097 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
Interest check: Peak EWMA (latency-aware P2C) LB policyPeut-être pris @markdroth l’a pris il y a 15 jours. Ouverteenhancement Type: Feature
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 35/100
grpc/grpc-java#13092 · 2 commentaires · 1 personne assignée ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de grpc/grpc-java
Issues similaires
-
Good First Issue
Difficulté 1/5 Moins d'une heure Accessibilité débutants 75/100
hiero-ledger/hiero-sdk-java#3025 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 80/100
-
DogFooding
Difficulté 1/5 Moins d'une heure Accessibilité débutants 88/100
OpenAEV-Platform/openaev#8768 ·
Les mainteneurs répondent en général sous 1 jour
-
Empty trash: cancelling the password prompt, then swiping one note restores every trashed noteOuverte
Difficulté 2/5 1-3 heures Accessibilité débutants 82/100
federicoiosue/Omni-Notes#1039 ·
-
type: bug
Difficulté 2/5 1-3 heures Accessibilité débutants 64/100
micronaut-projects/micronaut-core#14179 ·
Les mainteneurs répondent en général sous 1 jour