perf(ws_agent): optimize subscription speed, isolate sync handlers, fix task leaks#41
Merged
Merged
Conversation
…ix task leaks
WSAgent 4가지 최적화·안정성 개선.
1. 순차 구독 속도 N배 개선
- _subscribe_all에서 구독 사이 0.5초 sleep 제거
- _send_subscription이 응답 대기로 자연 직렬화되므로 추가 sleep 불필요
- 50 종목 구독 시 ~25초 단축 (이전: 0.5s × N 추가 소비)
2. Sync 핸들러 격리 — 수신 루프 블록 방지
- _call_handler에서 sync 핸들러를 asyncio.to_thread()로 격리
- 이전: 무거운 sync 핸들러가 _receive_loop 자체를 블록 → ping/pong 응답
지연 → 강제 재연결 발생 가능
- 핸들러 예외도 _call_handler가 잡아 stats["errors"] 증가만 시키고 다른
메시지 처리는 계속 진행
3. Background task 참조 보관 + 정리
- subscribe()/unsubscribe()의 fire-and-forget asyncio.create_task가
반환 task를 어디에도 저장하지 않아 GC로 사라질 위험 (Python 3.10+
문서화된 동작)
- _background_tasks set + _track_task() 헬퍼로 강한 참조 보관, 완료 시
done_callback으로 자동 제거
- disconnect() 시 미완료 task 일괄 cancel + gather
4. 구독 응답 state 정리 누수 fix + JSON 이중 파싱 제거
- _send_subscription의 finally에서 _subscription_results/_errors도
pop (이전엔 _pending만 정리되어 재시도 시 stale 데이터 사용 위험)
- _handle_message에서 JSON 파싱한 결과를 _parse_message에 넘겨
이중 json.loads() 호출 제거
검증:
- 신규 단위 테스트 9개 (tests/unit/test_ws_agent_optimizations.py):
* _subscribe_all 5개 구독이 0.3초 미만에 완료
* sync 핸들러가 메인 스레드와 다른 스레드에서 실행됨 (격리)
* async 핸들러는 이벤트 루프에서 직접 await
* 핸들러 예외가 _call_handler에서 잡혀 전파되지 않음
* _track_task가 done_callback으로 자동 제거됨
* disconnect가 미완료 task 모두 cancel
* _send_subscription 실패 후 pending/results/errors 모두 정리
* _parse_message가 preparsed json_data 재사용 (이중 파싱 회피)
- 기존 ws 단위 테스트 144/145 passed (1 pre-existing failure: test_fatal_
error_patterns_exist — 본 변경과 무관)
- ruff: All checks passed
📊 Coverage Analysis Report
✅ All files meet the target coverage of 70%! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WSAgent의 4가지 최적화·안정성 개선:
Why
1. 구독 속도
`_subscribe_all`이 각 구독 후 `await asyncio.sleep(0.5)` — KIS rate limit 우려로 추가됐지만 `_send_subscription` 자체가 응답 이벤트를 기다려 자연스럽게 직렬화됨. 50종목 구독 시 추가로 ~25초 소비. 응답 후 즉시 다음 요청을 보내도 KIS rate limit에 문제 없음.
2. Sync 핸들러 블로킹
`_call_handler`가 sync 핸들러를 직접 호출 → CPU/IO 무거운 핸들러가 `_receive_loop`를 통째로 블록 → ping/pong 응답 지연 → 강제 재연결. `asyncio.to_thread()`로 격리하면 핸들러가 무거워도 수신 루프는 계속 동작.
3. Task GC 위험
`subscribe()`의 `asyncio.create_task(self._send_subscription(...))`와 `unsubscribe()`의 fire-and-forget이 반환 task를 보관 안 함. Python 3.10+에서 weakly-referenced task가 GC로 사라질 수 있다는 문서화된 동작. disconnect 시 정리도 안 됨.
4. State 누수 + 이중 파싱
Changes
Test plan
Compatibility