You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

grpc-java双向流RPC:服务端无法接收客户端自定义异常

双向流RPC客户端自定义异常无法传递至服务端的解决方法

问题场景

在双向流RPC场景下,客户端调用outbound StreamObserver.onError时,传入构造好的INVALID_ARGUMENT类型StatusRuntimeException,但服务端通过inbound StreamObserver.onError接收到的却是io.grpc.StatusRuntimeException: CANCELLED: client canceled,而非预期的异常,且元数据为空。

客户端代码

@Override
public void onNext(@NonNull ActionReq req) {
    String actionId = req.getId();
    String actionName = req.getName();
    Log.d(TAG, String.format("onNext: id:%s,name:%s", actionId, actionName));
    if (!mActionId.equals(actionId)) {
        Metadata metadata = new Metadata();
        metadata.put(GrpcKeyUtil.sMetaActorActionIdKey, mActionId);
        StatusRuntimeException e = Status.INVALID_ARGUMENT
                .withDescription("actionId is error! stream.id:" + mActionId + ",req.id:" + actionId)
                .asRuntimeException(metadata);
        // mPeer是客户端的outbound StreamObserver
        mPeer.onError(e);
        return;
    }
    // ...
}

服务端代码

@Override
public void onError(Throwable t) {
    Log.e(TAG, "onError: ", t);
    if (t instanceof StatusRuntimeException e) {
        Metadata metadata = e.getTrailers();
        Log.d(TAG, "onError: metadata " + metadata);
    }
    actionResult.onError(t);
}

原因分析

gRPC客户端在**接收服务端消息的onNext回调中直接调用outbound的onError**时,会触发客户端默认的流取消逻辑:此时客户端的inbound流仍处于活跃状态,gRPC会将该错误触发行为判定为主动取消整个RPC连接,因此向服务端发送CANCELLED状态,覆盖自定义的INVALID_ARGUMENT异常。

另外,如果调用onError后仍执行其他流操作(如onNext/onCompleted),也会导致自定义异常被覆盖,服务端仅收到取消状态。

解决方案

1. 调整错误触发时机,避免在inbound回调中直接调用outbound.onError

不要在客户端接收服务端消息的onNext回调内直接触发outbound的错误,可将错误处理逻辑放到独立线程或异步回调中,确保客户端流状态稳定后再发送自定义异常:

@Override
public void onNext(@NonNull ActionReq req) {
    String actionId = req.getId();
    String actionName = req.getName();
    Log.d(TAG, String.format("onNext: id:%s,name:%s", actionId, actionName));
    if (!mActionId.equals(actionId)) {
        // 切换到独立线程处理错误,避免在inbound回调中触发流取消逻辑
        Executors.newSingleThreadExecutor().execute(() -> {
            Metadata metadata = new Metadata();
            metadata.put(GrpcKeyUtil.sMetaActorActionIdKey, mActionId);
            StatusRuntimeException e = Status.INVALID_ARGUMENT
                    .withDescription("actionId is error! stream.id:" + mActionId + ",req.id:" + actionId)
                    .asRuntimeException(metadata);
            mPeer.onError(e);
        });
        return;
    }
    // ...
}

2. 确保错误调用是outbound StreamObserver的最终操作

调用onError后,绝对不能再对同一个StreamObserver调用onNext或onCompleted,否则会触发gRPC的未定义行为,导致自定义异常被覆盖。

3. 验证元数据Key的一致性

确保客户端和服务端使用的Metadata.Key完全一致(包括名称、类型和编码方式),否则服务端无法解析到传递的元数据:

// 客户端和服务端需共用相同的Key定义
public static final Metadata.Key<String> sMetaActorActionIdKey =
        Metadata.Key.of("actor-action-id", Metadata.ASCII_STRING_MARSHALLER);

4. 禁用客户端不必要的自动取消配置

检查客户端Stub的配置,若开启了withWaitForReady()等可能触发自动取消的选项,需调整配置,确保自定义异常能正常传递。

内容的提问来源于stack exchange,提问作者Seven Sir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 08:53:16