How DS2API Implements Account Pool Rotation with Token Refresh
DS2API implements account pool rotation using a round-robin queue mechanism in internal/account while automatically refreshing tokens via time-based checks in internal/auth/request.go based on a configurable interval.
DS2API (CJackHwang/ds2api) distributes API requests across multiple managed DeepSeek/OpenAI accounts using a sophisticated pooling system. The architecture separates account lifecycle management from authentication logic, ensuring both account pool rotation and token refresh occur transparently to API callers. This implementation guarantees fair load distribution while maintaining valid credentials through proactive renewal.
The Account Pool Rotation Mechanism
The rotation system centers on a thread-safe pool that distributes accounts using a round-robin strategy. According to the source code in internal/account, the pool maintains a queue of account identifiers and tracks which accounts are currently in use.
Pool Initialization and Account Sorting
When the pool starts, NewPool creates the structure and immediately invokes Reset() to prepare the rotation queue. As implemented in internal/account/pool_core.go:22-71, the Reset method performs several critical setup steps:
- Sorts the underlying accounts so those with existing tokens appear first, prioritizing ready-to-use credentials.
- Constructs a queue of account identifiers that determines the rotation order.
- Records the recommended concurrency and rate limits for each account slot.
This initialization ensures the pool begins with a clear, deterministic order while respecting account capabilities.
Round-Robin Acquisition with bumpQueue
When a request needs an account, it calls Acquire or AcquireWait, both of which lock the pool and delegate to acquireLocked. Located in internal/account/pool_acquire.go:49-80, the acquisition logic scans the queue for the first available identifier that is not excluded or already in use.
Once an account is selected, the pool performs two atomic operations in internal/account/pool_acquire.go:83-91:
- Increments
inUse[id]to mark the account as active. - Calls
bumpQueue(id)to move the identifier to the end of the queue.
This bumpQueue operation implements the round-robin rotation. The next request receives the subsequent account in the queue, ensuring traffic spreads evenly across all configured credentials rather than hammering the same account repeatedly.
Safe Release and Concurrency Management
After request completion, Release decrements the in-use counter for the specific account ID. As shown in internal/account/pool_core.go:83-100, when the count reaches zero, the slot becomes available and the method notifies any waiting goroutines via the pool's condition variable. This mutex-protected flow prevents race conditions while maintaining high throughput across concurrent API calls.
Token Refresh Strategy and Implementation
While the pool handles rotation, the internal/auth package ensures tokens remain valid. The resolver implements a time-based refresh strategy that checks credentials before each request and automatically renews expired tokens.
Configurable Refresh Intervals
The system reads the refresh cadence from configuration field runtime.token_refresh_interval_hours, defined in internal/config/config.go:63-68. The default interval is 6 hours, though operators can adjust this based on the backend provider's token lifetime requirements.
The Token Freshness Check
The authentication resolver maintains a tokenRefreshedAt map in internal/auth/request.go:43-46 that tracks the last successful refresh timestamp for each account ID. Before using an acquired account, the code calls ensureManagedToken (internal/auth/request.go:50-62), which implements the following decision tree:
- If the account's token string is empty, trigger
loginAndPersist. - If
shouldForceRefreshreturns true (implemented ininternal/auth/request.go:64-84), compare the elapsed time sincetokenRefreshedAt[id]against the configured interval; if expired, triggerloginAndPersist. - Otherwise, reuse the existing cached token.
The shouldForceRefresh method calculates whether time.Now().Sub(lastRefresh) >= interval, providing a thread-safe check protected by the resolver's mutex.
Automatic Login and Persistence
When refresh is required, loginAndPersist (located in internal/auth/request.go:50-60) executes the injected Login function to obtain a new token from the upstream provider. Upon success, it:
- Stores the new token on the account object.
- Calls
markTokenRefreshedNow(internal/auth/request.go:86-92) to recordtime.Now()in thetokenRefreshedAtmap. - Updates the persistent store to survive restarts.
If a token is explicitly invalidated (for example, after a 401 response), MarkTokenInvalid removes the account's entry from the refresh map (internal/auth/request.go:74-82), forcing a fresh login on the next request.
Complete Request Flow Example
The following Go snippet illustrates how these systems interact during a typical API request:
// Step 1: Resolve the caller → triggers acquireManagedRequestAuth
authReq, _ := resolver.Determine(req)
// Step 2: Inside acquireManagedRequestAuth:
// a) pool.AcquireWait(...) gives us `acc` (rotated via bumpQueue)
// b) ensureManagedToken checks token freshness
// c) If needed, loginAndPersist refreshes the token and updates tokenRefreshedAt
Every request therefore experiences fair account selection and automatic token renewal without callers managing credentials manually. The refreshed token is cached in RequestAuth.DeepSeekToken for the duration of the request.
Summary
- Round-robin rotation is achieved via
bumpQueueininternal/account/pool_acquire.go, which moves used account IDs to the end of the queue after each acquisition. - Pool initialization in
internal/account/pool_core.gosorts accounts with existing tokens first and configures concurrency limits duringReset(). - Token refresh uses a configurable interval (
runtime.token_refresh_interval_hours) checked byshouldForceRefreshininternal/auth/request.go. - Automatic renewal occurs through
ensureManagedTokenandloginAndPersist, which handle both empty tokens and expired intervals while updating the persistent store. - Thread safety is maintained throughout via mutex protection in both the account pool and authentication resolver.
Frequently Asked Questions
How does DS2API prevent the same account from being used concurrently by multiple requests?
The pool uses a mutex-protected inUse map that tracks active request counts per account ID. When Acquire selects an account, it increments inUse[id] before releasing the lock, and Release decrements the count when the request completes. This mechanism ensures that accounts adhere to their configured concurrency limits while remaining available for round-robin rotation.
What triggers a token refresh in DS2API?
A token refresh triggers under two conditions in internal/auth/request.go: when the account's token string is empty (initial state), or when shouldForceRefresh detects that the elapsed time since the last refresh (stored in tokenRefreshedAt) exceeds the token_refresh_interval_hours configuration. Explicit invalidation via MarkTokenInvalid also forces a refresh on the next access.
How does the round-robin rotation work in the account pool?
The rotation works through the bumpQueue method in internal/account/pool_acquire.go. After an account is acquired, its identifier moves to the end of the queue slice. Subsequent acquisition requests scan the queue from the beginning, naturally receiving the next available account in sequence. This creates a circular distribution of load across all managed accounts.
Where is the token refresh interval configured?
The refresh interval is defined in internal/config/config.go as TokenRefreshIntervalHours within the Runtime configuration struct. The default value is 6 hours, and the system reads this value during resolver initialization to determine when cached tokens have expired and require renewal.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →