Summary
I'm making Client service that interacts with other MicroService Server.
By using gRPC Channel, this client get some data from server. The connection is used frequently, and participants are fixed, so I reuse gRPC Channel and Stub to reduce channel creating cost.
This service performs well for each requests, and each test functions alone at one time. However in multiple testing, I found that only one test succeed, and the other would be failed with TimeOutError
(gRPC status -DEADLINE_EXCEEDED
) or stopped.
Interestingly, this problem is solved when I remove channel caching(@lru_cache
), or add pytest event_loop
fixture override for session(or module) scope. I found the second method in this question/answer.
Why this happens? What makes my test stop or fail? I guess that it is related with event loop, but don't know details.
Minimal Reproducible Example(MRE)
# mre.py
from functools import lru_cache
from grpclib.client import Channel
from config.config import current_config
from custom.protobuf.service import OtherServiceStub
@lru_cache(maxsize=None)
def get_cached_client() -> OtherServiceStub:
host, port = '127.0.0.1', 50051
channel = Channel(host, port)
cached_client = OtherServiceStub(channel)
return cached_client
async def get_data(arg1: str = None):
client = get_cached_client()
data = client.get_detail_data(arg1='foo')
return data
# test_mre.py
@pytest.mark.asyncio
async def test_1(): # SUCCEED
client = get_cached_client()
await client.get_data(arg1='foo')
@pytest.mark.asyncio
async def test_2(): # FAIL(or STOP)
client = get_cached_client()
await client.get_data(arg1='bar')
@pytest.mark.asyncio
async def test_3(): # FAIL(or STOP)
client = get_cached_client()
await client.get_data(arg1='something')
# solved if(1)
# not cached
def get_cached_client() -> OtherServiceStub:
host, port = '127.0.0.1', 50051
channel = Channel(host, port)
cached_client = OtherServiceStub(channel)
return cached_client
# solved if(2)
# override event_loop fixture
@pytest.fixture(scope="session")
def event_loop(request):
"""Create an instance of the default event loop for each test case."""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
Environment
pytest==6.2.4
pytest-asyncio==0.15.1
grpcio==1.37.1
grpcio-tools==1.37.1
grpclib==0.4.1
protobuf==3.15.8
betterproto==1.2.5