Python GRPC How to reuse channel correctly

8k Views Asked by At

I am writing a GRPC python client to communicate with a GRPC server as below.

class GrpcClient:
    """
    Class to send request to Grpc server.
    """

    def __init__(self, host: str, port: int):
        self._server_address = host + ':' + str(port)
        with grpc.insecure_channel(self._server_address) as channel:
            self._stub = MyServerStub(channel)

    def invoke_method(self, request):
        response = self._stub.process(request)
        logging.info("response received: " + str(response))

This result in error ValueError: Cannot invoke RPC on closed channel! since the channel was getting closed on int I modified as below

    def __init__(self, host: str, port: int):
        self._server_address = host + ':' + str(port)
        channel = grpc.insecure_channel(self._server_address)
        self._stub = MyServerStub(channel)

Now both the channel and stub gets reused but my worry is that the channel might not be closed which might lead into memory leak

To prevent it I tried opening the channel and creating stub on each request which is expensive.

Can anyone suggest a way of reusing channel and stub without any leak?

1

There are 1 best solutions below

2
On

There are two ways you could tackle this problem, which are not necessarily mutually exclusive. The first is proxying the channel's close method to your class and requiring your class's user to call it. That would look something like this:

class GrpcClient:
  def __init_(self, ...):
    self._channel = grpc.insecure_channel(...)
    self._stub = MyServerStub(self._channel)

  def close(self):
    self._channel.close()

To make this API a bit cleaner, you could provide a context manager API similar to what grpc.Channel does:

class GrpcClient:
  ...
  def __enter__(self):
    return self
  
  def __exit_(self, ...):
    self.close()

Then you can use your class like

with GrpcClient(...) as client:
  for task in tasks:
    do_work(client, task)

People have attempted to use __del__ to automatically call such cleanup methods in the past, but we've found that the guarantees provided by CPython about when and if this method will be called are too weak to provide anything resembling C++ RAII, so we're left with manual options like I outlined above.