feat(quota): optimize quota refresh logic, add relative countdowns, parallelize card refreshes#45
Conversation
…arallelize card refreshes
| return; | ||
| } | ||
|
|
||
| state.refreshQueue.push(profile); | ||
| rerenderDashboard(); | ||
| void drainRefreshQueue(); | ||
| void performProfileRefresh(profile); |
There was a problem hiding this comment.
🔴 Removing serial refresh queue allows concurrent refreshes that race on the shared refresh_runtime directory
The old drainRefreshQueue serialized all profile refreshes one-at-a-time via the refreshWorkerActive / refreshQueue pattern. The new performProfileRefresh fires each refresh immediately and concurrently (via void performProfileRefresh(profile)). While the guard at src-tauri/shared/front/actions.ts:288-294 prevents duplicate refreshes for the same profile, it allows concurrent refreshes for different profiles.
This introduces a race condition in the backend's refresh_via_app_server fallback path: both mac/runtime/refresh_runtime.rs:224-264 and win/runtime/refresh_runtime.rs:207-239 call prepare_refresh_runtime_home, which copies the requesting profile's auth.json into a single shared directory (refresh_runtime/, defined at src-tauri/shared/runtime/paths.rs:15). If two profiles both fall back to the app-server path concurrently, they race on writing their respective auth.json into the same directory, then each spawns a codex app-server process against the mixed state, and finally copies the result back. This can cause one profile to receive the other's auth tokens — a data integrity and security issue.
Why the fast HTTP path is unaffected
The primary try_refresh_via_chatgpt_api path uses direct HTTP calls against profile-specific auth.json files in the backup root and doesn't touch the shared runtime directory, so concurrent fast-path refreshes are safe. The race only surfaces when two or more profiles both fall through to the refresh_via_app_server fallback (e.g., non-OAuth profiles, or transient HTTP failures).
(Refers to lines 264-296)
Prompt for agents
The PR replaced the serial drainRefreshQueue worker (which processed one refresh at a time via a while loop guarded by refreshWorkerActive) with a concurrent performProfileRefresh that fires each refresh immediately. This is unsafe because the backend refresh_via_app_server fallback path on both macOS (mac/runtime/refresh_runtime.rs, function refresh_via_app_server) and Windows (win/runtime/refresh_runtime.rs, function refresh_via_app_server) shares a single refresh_runtime directory for staging auth.json and spawning the codex app-server process.
Two possible approaches:
1. Restore serialization in the front-end: keep the new refreshActiveProfiles array for UI state, but ensure only one performProfileRefresh runs at a time (e.g., a simple mutex/queue that awaits the previous refresh before starting the next).
2. Fix the backend: give each concurrent refresh its own isolated runtime directory (e.g., append the profile name or a unique suffix to the refresh_runtime path) so they don't share auth.json. This requires changes in both mac/runtime/refresh_runtime.rs and win/runtime/refresh_runtime.rs (or shared/runtime/paths.rs for the Windows path).
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
This PR optimizes the quota refresh and display logic in the Codex Account Switcher. It implements dynamic relative reset countdowns, parallelizes manual account card refreshes, and increases the OAuth skew buffer.
Key Changes
Backend
models.rs): UpdatedQuotaWindowto includereset_at_timestamp: Option<i64>(fully backward-compatible).chatgpt_api.rs): IncreasedEXPIRY_SKEW_SECONDSto300seconds to avoid frequent validation errors, and populatedreset_at_timestampinquota_window_from_rate_limit.codex_app_server.rs): Populatedreset_at_timestampfrom the standard RPC response'sresetsAtfield.session_usage.rs): Populatedreset_at_timestamp, updatednormalize_quota_windowto preserve it, and updated test cases.profiles_index.rs): Wrapped reload/rebuild/write slow-paths inload_profiles_indexwith a globalMutexto serialize concurrent updates toprofiles.jsonsafely.Frontend
types.ts&tauri.ts): Addedreset_at_timestamp: number | nullto the frontend interfaces and updated mock helpers.state.ts&actions.ts): Replaced serialized refresh queue/worker state with a parallelizedrefreshActiveProfiles: string[]list.actions.ts): Registered a lightweight interval/timer that runs every 15 seconds to trigger UI re-renders so countdowns update live.render.ts&i18n.ts): Added relative countdown calculations and new translation keys to render remaining times dynamically.Verification
cargo test(96 tests passed successfully).npm run build(compiled successfully with 0 TypeScript/bundler errors).