繁体   English   中英

从 gRPC 服务器检测客户端上下文破坏

[英]Detect client context destruction from gRPC server

我创建了一个异步 C++ gRPC 服务器,它提供了几个类似的 API,其签名类似于:

service Foo {
    rpc FunctionalityA(ARequest) returns (stream AResponse);
    rpc FunctionalityB(BRequest) returns (stream BResponse);
}

客户端创建一个通道来连接该服务,并使用来自不同线程的各种 RPC 调用,如下所示:

class FooClient {

// ...

    void FunctionalityA() {
        auto stub = example::Foo::NewStub(m_channel);
        grpc::ClientContext context;
        example::ARequest request;
        example::AResponse response;
        auto reader = stub->FunctionalityA(&context, request);
        for(int i = 0; i < 3; i++) {
            reader->Read(&response);
        }
    }

    void FunctionalityB() {
        auto stub = example::Foo::NewStub(m_channel);
        grpc::ClientContext context;
        example::BRequest request;
        example::BResponse response;
        auto reader = stub->FunctionalityB(&context, request);
        for(int i = 0; i < 3; i++) {
            reader->Read(&response);
        }
    }

// ...

};

int main() {
    // ...
    FooClient client(grpc::CreateChannel("127.0.0.1:12345", grpc::InsecureChannelCredentials()));
    auto ta = std::thread(&FooClient::FunctionalityA, &client);
    auto tb = std::thread(&FooClient::FunctionalityB, &client);
    // ...
}

我想实现服务器,以便:

  • 当 FunctionalityA 被调用时,它开始流式传输 AResponse 类型的对象
  • 当 FunctionalityB 被调用时,它开始流式传输 BResponse 类型的对象
  • 当用于调用 FunctionalityA 的上下文被取消时,AResponse 的流式传输结束
  • 当用于调用 FunctionalityB 的上下文被取消时,BResponse 的流式传输结束

我面临的问题是,即使与两个功能之一关联的 ClientContext 超出 scope (在示例中的 3 次读取之后),服务器也没有收到任何信息并继续写入,并且“ok”状态保持为真。 “ok”状态变为 false 并允许我仅在客户端断开连接时停止写入。

这是 gRPC 的预期行为吗? 客户端是否需要发送特定的“死亡之吻”消息以通知服务器停止在 stream 上写入?

为了完整起见,这里是一个功能服务器端的实现示例:

void FunctionalityB::ProcessRequest(bool ok, RequestState state) {
    if(!ok) {
        if(state == RequestState::START) {
            // the server has been Shutdown before this particular call got matched to an incoming RPC
            delete this;
        } else if(state == RequestState::WRITE || state == RequestState::FINISH) {
            // not going to the wire because the call is already dead (i.e., canceled, deadline expired, other side dropped the channel, etc).
            delete this;
        } else {
            // unhandled state
        }
    } else {
        if(state == RequestState::START) {
            // the RPC has indeed been started
            m_writer.Write(m_response, CreateTag(RequestState::WRITE));
            // the constructor of the functionality requests a new one to handle future new connections
            new FunctionalityB(m_completion_queue, m_service, m_worker);
        } else if(state == RequestState::WRITE) {
            // TODO do some real work
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            m_writer.Write(m_response, CreateTag(RequestState::WRITE)); // this write will continue forever, even after client stops reading and TryCancel its context
        } else if(state == RequestState::FINISH) {
            delete this;
        } else {
            // unhandled state
        }
    }
}

有两种方法可以检测服务器上的呼叫取消。

第一个是检查ServerContext::IsCancelled() 这是你可以在写之前检查的东西,在这种情况下可能没问题。 但是,在一般情况下,它可能并不理想,因为您的应用程序可能会在执行另一次写入之前等待一些其他事件(除了先前的写入完成),并且理想情况下,您希望在取消时以某种异步方式获得通知发生。

这让我想到了第二种方法,即在 RPC 开始之前通过调用ServerContext::AsyncNotifyWhenDone()来取消调用时在完成队列上请求一个事件。 这将为您提供取消的异步通知,但不幸的是,API 非常笨重并且有一些锋利的边缘。 (这在新的基于回调的 API中处理得更加干净,但是在我们完成EventEngine工作之前,API 在 OSS 中的性能并不那么好。)

我希望这个信息是有帮助的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM