I have to cythonize
a python script which contains an asyncio
gRPC
server.
The script is working well when is running with Python
.
I've written a non asyncio
version of the script and both python and cythonized variants are working without any problems.
The only problem is when using asyncio + cythonization.
spam.proto
syntax = "proto3";
message EchoRequest {
string data = 1;
}
message EchoResponse {
string data = 1;
}
service Spam {
rpc Echo (EchoRequest) returns (EchoResponse);
rpc EchoStream (stream EchoRequest) returns (stream EchoResponse);
}
srv.py
import asyncio
import grpc
import spam_pb2
import spam_pb2_grpc
class Spam(spam_pb2_grpc.SpamServicer):
async def Echo(self, request, context):
response = spam_pb2.EchoResponse(data=request.data)
return response
async def EchoStream(self, request_iterator, context):
async for request in request_iterator:
response = spam_pb2.EchoResponse(data=request.data)
yield response
async def main():
server = grpc.aio.server()
spam_pb2_grpc.add_SpamServicer_to_server(Spam(), server)
server.add_insecure_port('0.0.0.0:60000')
await server.start()
await server.wait_for_termination()
if __name__ == '__main__':
asyncio.run(main())
cli.py
import asyncio
import time
import grpc
import spam_pb2
import spam_pb2_grpc
async def _yield_request():
for _ in range(3):
yield spam_pb2.EchoRequest(data=time.asctime())
async def main():
channel = grpc.aio.insecure_channel('localhost:60000')
stub = spam_pb2_grpc.SpamStub(channel)
response = await stub.Echo(spam_pb2.EchoRequest(data=time.asctime()))
print(response)
response_iterator = stub.EchoStream(_yield_request())
async for response in response_iterator:
print(response)
if __name__ == '__main__':
asyncio.run(main())
gen-pb2.sh
#!/bin/sh
python -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. spam.proto
build.sh
#!/bin/sh
cython -3 --embed --output-file srv.c srv.py
gcc $(pkg-config --cflags python3) srv.c -o srv -lpython3.8
srv
$ PYTHONPATH=$PYTHONPATH:venv/lib/python3.8/site-packages ./srv
/usr/lib/python3.8/asyncio/base_events.py:1860: RuntimeWarning: coroutine 'Spam.Echo' was never awaited
handle = None # Needed to break cycles when an exception occurs.
stub.Echo()
$ python cli.py
Traceback (most recent call last):
File "cli.py", line 27, in <module>
asyncio.run(main())
File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "cli.py", line 18, in main
response = await stub.Echo(spam_pb2.EchoRequest(data=time.asctime()))
File "/home/slava/cython-grpc-aio/venv/lib/python3.8/site-packages/grpc/aio/_call.py", line 290, in __await__
raise _create_rpc_error(self._cython_call._initial_metadata,
grpc.aio._call.AioRpcError: <AioRpcError of RPC that terminated with:
status = StatusCode.UNKNOWN
details = "Unexpected <class 'TypeError'>: descriptor 'SerializeToString' for 'google.protobuf.pyext._message.CMessage' objects doesn't apply to a 'coroutine' object"
debug_error_string = "{"created":"@1636478705.359130370","description":"Error received from peer ipv6:[::1]:60000","file":"src/core/lib/surface/call.cc","file_line":1069,"grpc_message":"Unexpected <class 'TypeError'>: descriptor 'SerializeToString' for 'google.protobuf.pyext._message.CMessage' objects doesn't apply to a 'coroutine' object","grpc_status":2}"
>
stub.EchoStream()
$ python cli.py
Traceback (most recent call last):
File "cli.py", line 27, in <module>
asyncio.run(main())
File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "cli.py", line 22, in main
async for response in response_iterator:
File "/home/slava/cython-grpc-aio/venv/lib/python3.8/site-packages/grpc/aio/_call.py", line 326, in _fetch_stream_responses
await self._raise_for_status()
File "/home/slava/cython-grpc-aio/venv/lib/python3.8/site-packages/grpc/aio/_call.py", line 236, in _raise_for_status
raise _create_rpc_error(await self.initial_metadata(), await
grpc.aio._call.AioRpcError: <AioRpcError of RPC that terminated with:
status = StatusCode.UNKNOWN
details = "Unexpected <class 'TypeError'>: 'async_generator' object is not iterable"
debug_error_string = "{"created":"@1636479008.742822012","description":"Error received from peer ipv6:[::1]:60000","file":"src/core/lib/surface/call.cc","file_line":1069,"grpc_message":"Unexpected <class 'TypeError'>: 'async_generator' object is not iterable","grpc_status":2}"
>