Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

binder: RuntimeException during outbound serialization leaks the call and never notifies the peer

Đang mở
#13,093 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@mvanhorn đang làm issue này rồi.

Từ ngày 9/10/2026.

  • #13106 của @mvanhorn — đang mở

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
48/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
android, java
Lĩnh vực
backend-api-design

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

bug
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 graceful shutdown() 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:

  1. Register a unary echo method (ServerCalls.asyncUnaryCall) wrapped in a ServerInterceptor whose SimpleForwardingServerCall.close() does trailers.put(POISON_KEY, ne w ThrowingParcelable()) before delegating, and a second interceptor that records whether the app Listener gets onComplete() or onCancel().
  2. From the client, ClientCalls.futureUnaryCall(channel.newCall(METHOD, CallOptions.DEFAULT.withDeadlineAfter(2, SECONDS)), Empty.getDefaultInstance()).get(10, SECONDS).
  3. Assert the status is not DEADLINE_EXCEEDED → fails (it is DEADLINE_EXCEEDED).
  4. Assert the recorded server listener outcome is onCancel → fails (it is onComplete).
  5. 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:

  1. Complete one clean unary RPC on the channel first, so the binder transport is READY and the next start() reaches the binder stream synchronously (otherwise DelayedClientTransport defers it to the app executor and the exception surfaces there instead).
  2. Wrap the channel with a ClientInterceptor whose SimpleForwardingClientCall.start() does headers.put(POISON_KEY, new ThrowingParcelable()), and newCall() a BIDI_STREAMING method with withDeadlineAfter(2, SECONDS).
  3. call.start(listener, new Metadata()). Assert it does not throw → fails (IllegalStateException: writeToParcel() failed).
  4. Assert listener.onClose() is invoked within 10 s → fails (TimeoutException; the deadline never fires).
  5. channel.shutdown(); assertTrue(channel.awaitTermination(10, SECONDS)) → fails.
Ngôn ngữ chính
Java
Star
12.1k
Fork
4k
Merge trung bình
2 ngày 6 giờ
Pull request đã merge (30 ngày)
26

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của grpc/grpc-java

Tất cả issue của grpc/grpc-java

Issue tương tự

Thêm issue về Java

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.