소주제 : async Server 의 singal handler 를 추가하여, 보다 안전하게 서버를 종료하고 싶다.
수정하게 된 계기
처음 async server 에 대해서, 참조를 했던 코드는 GRPC python example 에서 참조하여, 구현하여 아래와 같이 구현하였으나,
try:
await server.wait_for_termination()
except KeyboardInterrupt:
redis.close()
await redis.wait_closed() # Coroutine waiting until underlying connections are closed.
await database.disconnect()
await server.stop(0)
try exception 이 asyncio 의 EventLoop 단에서 잡혀서, 저 아래까지 내려오지 않는 것을 확인할 수 있었고,
서버를 종료할 떄, DB 에 대한 컨넥션이 제대로 종료가 안되는 점을 발견하면서, 이 부분에 대해서 수정할 필요가 있다는 것을 느꼈습니다.
또한, 서버(grpc server)는 종료가 되었으나, python 프로세스는 종료되지 않은 것을 확인하여, 이를 수정할 필요성을 느꼈습니다.
해결 방법 (signal handler 추가)
FastAPI 나 django, flask 등 대다수의 라이브러리들은 before shut down 과 같은 타이밍에 처리하는 함수들이 있기에,
asyncio 에서도 이는 존재할것이라 생각하였습니다.
그래서 찾은 것이 loop.add_signal_handler 이였습니다.
async def shut_down_server(signal, loop):
global redis, server
redis.close()
await redis.wait_closed() # Coroutine waiting until underlying connections are closed.
await database.disconnect()
await server.stop(0)
loop.stop()
# ------------------------------------
# 이전 방식
# asyncio.run(serve())
# 현재방식
loop = asyncio.get_event_loop()
signals = (signal.SIGHUP, signal.SIGTERM, signal.SIGINT)
for s in signals:
loop.add_signal_handler(s, lambda s=s: asyncio.create_task(shut_down_server(s, loop)))
try:
loop.run_until_complete(serve())
finally:
loop.close()
asyncio.run() 의 경우, 함수 내부에서, loop = events.new_event_loop() 를 통해서, 새로운 EvenetLoop 를 생성하기에 loop 에 대한 signal_handler 를 지정할 수 없기에, 쓸 수 없었습니다.
접기/펼치기 버튼
```python
def run(main, *, debug=False):
"""Execute the coroutine and return the result.
This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous
generators.
This function cannot be called when another asyncio event loop is
running in the same thread.
If debug is True, the event loop will be run in debug mode.
This function always creates a new event loop and closes it at the end.
It should be used as a main entry point for asyncio programs, and should
ideally only be called once.
Example:
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
"""
if events._get_running_loop() is not None:
raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
if not coroutines.iscoroutine(main):
raise ValueError("a coroutine was expected, got {!r}".format(main))
loop = events.new_event_loop()
try:
events.set_event_loop(loop)
loop.set_debug(debug)
return loop.run_until_complete(main)
finally:
try:
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
events.set_event_loop(None)
loop.close()
```
위와 같은 형태로 수정을 하였으며, 아래처럼 무언가가 올바르게 DB 와의 연결이 종료되고, python 프로세스 또한 안전하게 종료된것을 확인할 수 있었습니다.
그리고, 위에서, 아래와 같은 경고문이 떠서 확인을 해보았는데,
app.py:123: DeprecationWarning: The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.
loop.run_until_complete(serve())
위의 사진에 있는, asyncio 함수들중 loop 를 추가 인자로 받는 것이 앞으로 사라진다는 의미였고, 저 warning 에 대해서는 걱정하지 않으셔도 됩니다.
수정하게 된 계기
처음
async server
에 대해서, 참조를 했던 코드는 GRPC python example 에서 참조하여, 구현하여 아래와 같이 구현하였으나,try exception
이asyncio 의 EventLoop
단에서 잡혀서, 저 아래까지 내려오지 않는 것을 확인할 수 있었고,서버를 종료할 떄,
DB
에 대한 컨넥션이 제대로 종료가 안되는 점을 발견하면서, 이 부분에 대해서 수정할 필요가 있다는 것을 느꼈습니다.또한,
서버(grpc server)
는 종료가 되었으나,python 프로세스
는 종료되지 않은 것을 확인하여, 이를 수정할 필요성을 느꼈습니다.해결 방법 (signal handler 추가)
FastAPI
나django
,flask
등 대다수의 라이브러리들은before shut down
과 같은 타이밍에 처리하는 함수들이 있기에,asyncio
에서도 이는 존재할것이라 생각하였습니다.그래서 찾은 것이
loop.add_signal_handler
이였습니다.위와 같은 형태로 수정을 하였으며, 아래처럼 무언가가 올바르게
DB
와의 연결이 종료되고,python 프로세스
또한 안전하게 종료된것을 확인할 수 있었습니다.그리고, 위에서, 아래와 같은 경고문이 떠서 확인을 해보았는데,
위의 사진에 있는,
asyncio
함수들중loop
를 추가 인자로 받는 것이 앞으로 사라진다는 의미였고, 저warning
에 대해서는 걱정하지 않으셔도 됩니다.