fix: add token refresh lock to prevent concurrent API calls (issue #42) #46

Merged
jihoson merged 1 commits from feature/issue-42-token-refresh-lock into main 2026-02-05 00:11:04 +09:00
Collaborator

Summary

  • Add asyncio.Lock to prevent concurrent token refresh attempts
  • Implement double-check locking pattern for token validation
  • Prevent "1분당 1회" rate limit errors (EGW00133)

Problem

Docker logs showed token refresh failures:

"접근토큰 발급 잠시 후 다시 시도하세요(1분당 1회)" (EGW00133)
Token refresh failed (403)

Multiple coroutines detect expired token simultaneously and all try to refresh,
hitting KIS API's 1-per-minute token refresh rate limit.

Solution

class KISBroker:
    def __init__(self, settings):
        self._token_lock = asyncio.Lock()
    
    async def _ensure_token(self) -> str:
        # Fast path: check without lock
        if self._access_token and now < self._token_expires_at:
            return self._access_token
        
        # Slow path: acquire lock and refresh
        async with self._token_lock:
            # Re-check after acquiring lock (double-check pattern)
            if self._access_token and now < self._token_expires_at:
                return self._access_token
            # ... refresh token

Test Coverage

  • Test concurrent refresh (5 parallel requests → 1 API call)
  • All existing tests pass (277 total, +1 new)
  • Coverage maintained at 80%

Performance

  • No performance impact on cache hit (fast path has no lock)
  • Only locks during actual refresh (rare occurrence)

Closes

Closes #42

🤖 Generated with Claude Code

## Summary - Add `asyncio.Lock` to prevent concurrent token refresh attempts - Implement double-check locking pattern for token validation - Prevent "1분당 1회" rate limit errors (EGW00133) ## Problem Docker logs showed token refresh failures: ``` "접근토큰 발급 잠시 후 다시 시도하세요(1분당 1회)" (EGW00133) Token refresh failed (403) ``` Multiple coroutines detect expired token simultaneously and all try to refresh, hitting KIS API's 1-per-minute token refresh rate limit. ## Solution ```python class KISBroker: def __init__(self, settings): self._token_lock = asyncio.Lock() async def _ensure_token(self) -> str: # Fast path: check without lock if self._access_token and now < self._token_expires_at: return self._access_token # Slow path: acquire lock and refresh async with self._token_lock: # Re-check after acquiring lock (double-check pattern) if self._access_token and now < self._token_expires_at: return self._access_token # ... refresh token ``` ## Test Coverage - ✅ Test concurrent refresh (5 parallel requests → 1 API call) - ✅ All existing tests pass (277 total, +1 new) - ✅ Coverage maintained at 80% ## Performance - No performance impact on cache hit (fast path has no lock) - Only locks during actual refresh (rare occurrence) ## Closes Closes #42 🤖 Generated with [Claude Code](https://claude.com/claude-code)
agentson added 1 commit 2026-02-05 00:09:22 +09:00
fix: add token refresh lock to prevent concurrent API calls (issue #42)
Some checks failed
CI / test (pull_request) Has been cancelled
95f540e5df
Add asyncio.Lock to prevent multiple coroutines from simultaneously
refreshing the KIS access token, which hits the 1-per-minute rate
limit (EGW00133: "접근토큰 발급 잠시 후 다시 시도하세요").

Changes:
- Add self._token_lock in KISBroker.__init__
- Wrap token refresh in async with self._token_lock
- Re-check token validity after acquiring lock (double-check pattern)
- Add concurrent token refresh test (5 parallel requests → 1 API call)

The lock ensures that when multiple coroutines detect an expired token,
only the first one refreshes while others wait and reuse the result.

Fixes: #42

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
jihoson merged commit 5e4c68c9d8 into main 2026-02-05 00:11:04 +09:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jihoson/The-Ouroboros#46