【问题标题】:Memory leaks happen when reconnecting in gRPC c++在 gRPC c++ 中重新连接时发生内存泄漏
【发布时间】:2022-01-14 05:31:22
【问题描述】:

Windows、visual studio2017、C++、gRPC-1.40.0

代码以hello world为基础,增加如下流程。

  1. 输入“x”,应用程序将终止,Protocol Buffers 将被释放。
  2. 输入“x”以外的内容,创建一个通道并向服务器发出请求。

根据输入“x”以外的次数,应用程序结束时检测到的未释放内存量会增加。

-客户-

#include <iostream>
#include <Windows.h>
struct cleanup_t
{
    ~cleanup_t() { if (IsDebuggerPresent()) _CrtDumpMemoryLeaks(); }
} cleanup;

#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include <grpc/support/log.h>

#include "../common/helloworld.grpc.pb.h"

using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloReply;
using helloworld::Greeter;

class GreeterClient{
public:
    explicit GreeterClient(std::shared_ptr<Channel> channel)
        : stub_(Greeter::NewStub(channel)) {}

    std::string SayHello(const std::string& user){
        HelloRequest request;
        request.set_name(user);

        HelloReply reply;
        ClientContext context;

        Status status = stub_->SayHello(&context, request, &reply);

        if (status.ok()) {
            return reply.message();
        }
        else {
            std::cout << status.error_code() << ": " << status.error_message()  << std::endl;
            return "RPC failed";
        }
    }

private:
    std::unique_ptr<Greeter::Stub> stub_;
};

int main(int argc, char** argv){
    std::cout << "****************" << std::endl;
    std::cout << "Start Client" << std::endl;
    std::cout << "Finish:x" << std::endl;
    std::cout << "****************\n" << std::endl;

    bool exit = false;
    while ( !exit ){
        char buffer[128];
        printf(">");
        fgets(buffer, 128, stdin);

        if (strcmp(buffer, "x\n") == 0) {
            exit = true;
        }
        else{
            GreeterClient greeter(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));
            std::string reply = greeter.SayHello("World");  // The actual RPC call!
            std::cout << "Greeter received: " << reply << std::endl;
        }
    }
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

-服务器-

#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>

#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "../common/helloworld.grpc.pb.h"
#endif

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;

class GreeterServiceImpl final : public Greeter::Service {
    Status SayHello(ServerContext* context, const HelloRequest* request,
        HelloReply* reply) override {
        std::string prefix("Hello ");
        reply->set_message(prefix + request->name());
        return Status::OK;
    }
};

void RunServer() {
    std::string server_address("0.0.0.0:50051");
    GreeterServiceImpl service;

    grpc::EnableDefaultHealthCheckService(true);
    grpc::reflection::InitProtoReflectionServerBuilderPlugin();
    ServerBuilder builder;

    builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());

    builder.RegisterService(&service);

    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server listening on " << server_address << std::endl;

    server->Wait();
}

int main(int argc, char** argv) {
    RunServer();

    return 0;
}

还在服务器端增加了一个可以检测内存泄漏的进程,经过测试,确认每次连接时服务器的内存也增加了。


顺便说一句,你可能认为不应该每次都创建GreeterClient 类并抛出请求,但是下面的代码也会发生内存泄漏。

-产生内存泄漏的过程-

  1. 在客户端运行的情况下重新启动服务器。
  2. 输入“x”以外的内容。

根据执行上述步骤的次数,在应用程序结束时检测到的未释放内存量会增加。

-客户-

#include <iostream>
#include <Windows.h>
struct cleanup_t
{
    ~cleanup_t() { if (IsDebuggerPresent()) _CrtDumpMemoryLeaks(); }
} cleanup;

#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include <grpc/support/log.h>

#include "../common/helloworld.grpc.pb.h"

using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloReply;
using helloworld::Greeter;

class GreeterClient{
public:
    explicit GreeterClient(std::shared_ptr<Channel> channel)
        : stub_(Greeter::NewStub(channel)) {}

    std::string SayHello(const std::string& user){
        HelloRequest request;
        request.set_name(user);

        HelloReply reply;
        ClientContext context;

        Status status = stub_->SayHello(&context, request, &reply);

        if (status.ok()) {
            return reply.message();
        }
        else {
            std::cout << status.error_code() << ": " << status.error_message()  << std::endl;
            return "RPC failed";
        }
    }

private:
    std::unique_ptr<Greeter::Stub> stub_;
};

int main(int argc, char** argv){
    std::cout << "****************" << std::endl;
    std::cout << "Start Client" << std::endl;
    std::cout << "Finish:x" << std::endl;
    std::cout << "****************\n" << std::endl;

    GreeterClient greeter(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));

    bool exit = false;
    while ( !exit ){
        char buffer[128];
        printf(">");
        fgets(buffer, 128, stdin);

        if (strcmp(buffer, "x\n") == 0) {
            exit = true;
        }
        else{                
            std::string reply = greeter.SayHello("World");  // The actual RPC call!
            std::cout << "Greeter received: " << reply << std::endl;
        }
    }
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

我认为每次重新连接并抛出请求时都会发生内存泄漏。


正在发生内存泄漏。 附图显示了使用性能监视器在以下代码中监视客户端内存的结果。 内存监控在启动后一段时间开始。

int main(int argc, char** argv){
    std::cout << "****************" << std::endl;
    std::cout << "Start Client" << std::endl;
    std::cout << "Finish:x" << std::endl;
    std::cout << "****************\n" << std::endl;

    bool exit = false;
    while ( !exit ){
        char buffer[128];
        printf(">");
        fgets(buffer, 128, stdin);

        if (strcmp(buffer, "x\n") == 0) {
            exit = true;
        }
        else{
            while (true)
            {
                GreeterClient greeter(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));
                std::string reply = greeter.SayHello("World");  // The actual RPC call!
                Sleep(1);
            }
        }
    }
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

任何帮助表示赞赏。

【问题讨论】:

    标签: c++ visual-c++ memory-leaks grpc grpc-c++


    【解决方案1】:

    _CrtDumpMemoryLeaks 可能对 gRPC 的静态变量产生误报,因为它们在 main() 函数之后被释放。由于 gRPC 使用MemorySanitizer 来确保防止 gRPC 发生内存泄漏,因此您可以使用相同的工具来检查它是否是真正的内存泄漏。

    【讨论】:

    • 由于Visualstudio中内存清理器仍处于测试阶段,我添加了使用性能监视器监控内存的结果,以查看是否发生内存泄漏。似乎正在发生内存泄漏。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-08
    相关资源
    最近更新 更多