Joshua F
Joshua F

Reputation: 130

gRPC Python server to client communication through function call

Let's say I am building a simple chat app using gRPC with the following .proto:

service Chat {
    rpc SendChat(Message) returns (Void);
    rpc SubscribeToChats(Void) returns (stream Message);
}

message Void {}
message Message {string text = 1;}

The way I often see the servicer implemented (in examples) in Python is like this:

class Servicer(ChatServicer):
    def __init__(self):
        self.messages = []

    def SendChat(self, request, context):
        self.messages.append(request.text)
        return Void()

    def SubscribeToChats(self, request, context):
        while True:
            if len(self.messages) > 0:
                yield Message(text=self.messages.pop())    

While this works, it seems very inefficient to spawn an infinite loop that continuously checks a condition for each connected client. It would be preferable to instead have something like this, where the send is triggered right as a message comes in and doesn't require any constant polling on a condition:

class Servicer(ChatServicer):
    def __init__(self):
        self.listeners = []

    def SendChat(self, request, context):
        for listener in self.listeners:
            listener(Message(request.text))
        return Void()

    def SubscribeToChats(self, request, context, callback):
        self.listeners.append(callback)    

However, I can't seem to find a way to do something like this using gRPC.

I have the following questions:

  1. Am I correct that an infinite loop is inefficient for a case like this? Or are there optimizations happening in the background that I'm not aware of?
  2. Is there any efficient way to achieve something similar to my preferred solution above? It seems like a fairly common use case, so I'm sure there's something I'm missing.

Thanks in advance!

Upvotes: 0

Views: 1614

Answers (1)

Joshua F
Joshua F

Reputation: 130

I figured out an efficient way to do it. The key is to use the AsyncIO API. Now my SubscribeToChats function can be an async generator, which makes things much easier.

Now I can use something like an asyncio Queue, which my function can await on in a while loop. Similar to this:

class Servicer(ChatServicer):
    def __init__(self):
        self.queue = asyncio.Queue()

    async def SendChat(self, request, context):
        await self.queue.put(request.text)
        return Void()

    async def SubscribeToChats(self, request, context):
        while True:
            yield Message(text=await self.queue.get())

Upvotes: 0

Related Questions