@@ -754,8 +754,11 @@ async def handle_index(_request: web.Request) -> web.Response:
754754
755755
756756async def handle_api_portfolio (_request : web .Request ) -> web .Response :
757+ # live 모드에서는 KIS 잔고 조회(동기 네트워크)가 섞일 수 있다 — 스레드로 격리.
758+ import asyncio
759+
757760 try :
758- data = get_portfolio_json ( )
761+ data = await asyncio . to_thread ( get_portfolio_json )
759762 return web .json_response (data )
760763 except Exception as e :
761764 logger .exception ("API /api/portfolio 오류: {}" , e )
@@ -819,8 +822,14 @@ async def handle_api_cash_flows(request: web.Request) -> web.Response:
819822
820823
821824async def handle_api_runtime (_request : web .Request ) -> web .Response :
825+ # get_runtime_json은 시장 국면 실시간 조회(동기 네트워크, 수십 초 가능)를 포함한다 —
826+ # 이벤트 루프에서 직접 부르면 그동안 '/'·내 자산·차트까지 전부 멈춘다(첫 로드 체감 저하).
827+ # 스레드로 내려 다른 엔드포인트는 즉시 응답하게 한다.
828+ import asyncio
829+
822830 try :
823- return web .json_response (get_runtime_json ())
831+ data = await asyncio .to_thread (get_runtime_json )
832+ return web .json_response (data )
824833 except Exception as e :
825834 logger .exception ("API /api/runtime 오류: {}" , e )
826835 return web .json_response ({"error" : str (e )}, status = 500 )
@@ -851,16 +860,10 @@ async def handle_api_basket_evaluation(_request: web.Request) -> web.Response:
851860 read-only. include_benchmark=False로 네트워크(KS11 조회)를 피한다 — 대시보드는
852861 10초 폴링이므로 외부 조회를 섞으면 안 된다. 결과는 60초 TTL 캐시.
853862 """
863+ import asyncio
854864 import time as _time
855865
856- try :
857- now = _time .monotonic ()
858- if (
859- _BASKET_EVAL_CACHE ["data" ] is not None
860- and now - _BASKET_EVAL_CACHE ["at" ] < _BASKET_EVAL_TTL_SEC
861- ):
862- return web .json_response (_BASKET_EVAL_CACHE ["data" ])
863-
866+ def _collect_all () -> dict :
864867 from core .basket_evaluation import collect_basket_paper_evaluation
865868 from core .basket_rebalancer import BasketRebalancer
866869
@@ -877,7 +880,19 @@ async def handle_api_basket_evaluation(_request: web.Request) -> web.Response:
877880 "snapshot_coverage" : result .get ("snapshot_coverage" ),
878881 "issues" : result .get ("issues" , []),
879882 })
880- payload = {"evaluations" : out }
883+ return {"evaluations" : out }
884+
885+ try :
886+ now = _time .monotonic ()
887+ if (
888+ _BASKET_EVAL_CACHE ["data" ] is not None
889+ and now - _BASKET_EVAL_CACHE ["at" ] < _BASKET_EVAL_TTL_SEC
890+ ):
891+ return web .json_response (_BASKET_EVAL_CACHE ["data" ])
892+
893+ # 수집기는 TradingHours 초기화·(잠재) pykrx 갱신 등 동기 작업 — 스레드로 내려
894+ # 캐시 미스 시에도 이벤트 루프가 다른 요청을 계속 처리하게 한다.
895+ payload = await asyncio .to_thread (_collect_all )
881896 _BASKET_EVAL_CACHE ["at" ] = now
882897 _BASKET_EVAL_CACHE ["data" ] = payload
883898 return web .json_response (payload )
0 commit comments