Skip to content

perf(codex): single-flight CodexAuthStore refresh - #168

Merged
amondnet merged 10 commits into
mainfrom
amondnet/perf-add-single-flight-to-codexauthstore-refresh
Jul 15, 2026
Merged

perf(codex): single-flight CodexAuthStore refresh#168
amondnet merged 10 commits into
mainfrom
amondnet/perf-add-single-flight-to-codexauthstore-refresh

Conversation

@amondnet

@amondnet amondnet commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Applies single-flight coalescing to CodexAuthStore::refresh_and_write_back so concurrent requests hitting an expired ChatGPT credential no longer each race their own upstream token refresh. Refresh + writeback is detached onto a spawned task guarded by a process-wide async mutex, so a cancelled waiter can't interrupt another waiter's in-flight refresh; waiters that lose the race simply re-read the credential persisted by the winner. Adds a concurrent-refresh test covering this path.

Milestone / spec

N/A — internal performance/correctness hardening of the existing Codex auth refresh path (no spec deviation).

Checklist

  • cargo build passes
  • cargo test passes (new behavior is covered; tests run without network/loopback where possible)
  • cargo clippy --all-targets -- -D warnings clean
  • cargo fmt --all --check clean
  • Source files stay under 500 lines — src/auth/codex/auth.rs was already over 500 lines before this change; not introduced by this PR
  • English only; matches surrounding style
  • Frozen spec in docs/ updated if this change deviates from it — N/A, no spec deviation
  • User-facing docs updated for behavior/config/endpoint/CLI/provider/model changes — N/A, no observable behavior/config/endpoint/CLI change
  • Any new GitHub Action is pinned to a full commit SHA — N/A, no new GitHub Action

Notes for reviewers

Please look closely at the credential-handling path: the spawned refresh task must complete writeback even if the original caller is cancelled, and losing waiters must correctly re-read the winner's persisted credential rather than retrying their own refresh.

Related Issues

No linked issue.


Summary by cubic

Add single-flight coalescing to Codex ChatGPT OAuth refresh with a canonicalized, path-keyed lock to eliminate redundant token exchanges. Refresh + writeback runs in a detached, cancellation-safe task; paths are canonicalized for both locking and persistence, and different accounts refresh in parallel.

  • Bug Fixes
    • Canonicalize the lock and auth file path on a blocking thread; aliases share one lock and write to the same file, while different paths refresh concurrently.
    • Run refresh + writeback in a detached task that holds the lock through persistence; log detached refresh/validation/writeback failures.
    • Coalesce forced refreshes: only refresh if the stored access token matches the rejected token; waiters re-read the winner’s credential.
    • Handle unexpected credentials on 401s: safely extract the rejected token with chatgpt_access_token(); if not ChatGPT, cooldown the account and warn instead of refreshing.
    • Add tests for single-flight (one upstream call), alias normalization, parallel-path concurrency, cancellation still persisting the rotated token, and forced refresh coalescing.

Written for commit a99c3c1. Summary will update on new commits.

Concurrent requests hitting an expired ChatGPT credential each raced their
own refresh call, causing redundant upstream token exchanges and risking a
cancelled caller stranding a rotated refresh token before writeback. Guard
refresh_and_write_back with a process-wide async mutex and detach the
refresh+writeback sequence onto a spawned task so cancellation of one
waiter's future can't interrupt another's in-flight refresh; waiters that
lose the race simply re-read the credential persisted by the winner.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a single-flight mechanism for ChatGPT OAuth token refreshes using a static Mutex and detached background tasks to ensure cancellation safety during token rotation and writeback. The reviewer feedback points out a concurrency issue in force_refresh where multiple concurrent calls can cause redundant sequential refreshes, and suggests implementing a double-checked locking pattern to prevent this.

Comment thread src/auth/codex/auth.rs
@codspeed-hq

codspeed-hq Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 11 untouched benchmarks


Comparing amondnet/perf-add-single-flight-to-codexauthstore-refresh (a99c3c1) with main (1c65f7b)

Open in CodSpeed

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.00272% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/adapters/responses/pool.rs 64.70% 6 Missing ⚠️
src/auth/codex/auth.rs 98.56% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

@amondnet
amondnet marked this pull request as ready for review July 15, 2026 11:48
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a path-keyed single-flight registry to serialize concurrent ChatGPT OAuth token refreshes for the same credential file, while allowing different files to refresh in parallel. It also wraps the refresh and writeback sequence in a detached tokio::spawn task to ensure cancellation safety. The review feedback points out a medium-severity issue where token refresh errors inside the spawned task could be silently lost if the caller's future is cancelled before completion, and suggests wrapping the sequence in an async block to guarantee all errors are logged.

Comment thread src/auth/codex/auth.rs
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/auth/codex/auth.rs

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a single-flight mechanism for ChatGPT OAuth token refreshes in CodexAuthStore to prevent concurrent redundant refreshes for the same credential file while allowing parallel refreshes for different accounts. It also ensures that token writebacks are run in detached tasks so they are not lost if the caller's future is cancelled, and adds a conditional force-refresh method to skip rotation if another request has already updated the token. Extensive concurrent and cancellation tests are added to verify these behaviors. No review comments were provided, so there is no additional feedback.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/auth/codex/auth.rs Outdated
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces path-keyed single-flighting for ChatGPT OAuth token refreshes in CodexAuthStore to serialize concurrent refreshes of the same credential file while allowing different accounts to refresh in parallel. It also ensures that the refresh and writeback sequence runs in a detached task so that dropping the caller's future does not lose a rotated token. Feedback was provided regarding the synchronous path.canonicalize() call inside normalized_auth_path which is executed within an async context and could block the async executor thread.

Comment thread src/auth/codex/auth.rs
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements single-flighting and path normalization for ChatGPT OAuth token refreshes in CodexAuthStore to prevent redundant rotations and handle request cancellations safely. The review feedback highlights a potential panic in chatgpt_access_token when encountering non-OAuth credentials, suggesting to return an Option<&str> and handle it with a safe fallback at the call sites.

Comment thread src/adapters/responses/pool.rs Outdated
Comment thread src/adapters/responses/pool.rs Outdated
Comment thread src/adapters/responses/inbound.rs Outdated
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

본 풀 리퀘스트는 ChatGPT OAuth 토큰 갱신 메커니즘의 취소 안전성(cancellation safety)과 단일 실행(single-flight) 보장을 개선합니다. 동일한 자격 증명 파일에 대한 동시 갱신을 직렬화하기 위해 경로별 단일 실행 레지스트리를 도입하고, 호출자의 퓨처가 취소되더라도 토큰 갱신 및 파일 쓰기 작업이 완료되도록 tokio::spawn을 통해 비동기 작업을 분리했습니다. 리뷰어의 피드백은 이미 구현된 변경 사항을 중복 제안하고 있어 제외하였으며, 이에 따라 추가로 제공할 피드백은 없습니다.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds single-flight coalescing to CodexAuthStore so that concurrent requests sharing an expired ChatGPT OAuth credential make exactly one upstream token exchange. A process-wide REFRESH_LOCKS registry (keyed by canonical file path) serializes refreshes per credential file, while a detached tokio::spawn task owns the lock guard through writeback so cancelling a caller's future cannot strand a rotated refresh token in memory.

  • force_refresh_if_access_token is the new coalescing entry point: after acquiring the path lock, it re-reads the stored credential and skips the upstream call if another waiter has already rotated the token, returning the winner's persisted credential instead.
  • Cancellation safety is proven by the cancelled_refresh_still_persists_rotated_token test: aborting the outer task drops the JoinHandle without cancelling the inner spawn, so writeback always completes.
  • Path normalization via canonicalize() (with a fallback to the raw path) ensures symlink aliases share the same lock entry and credential file.

Confidence Score: 5/5

Safe to merge — the single-flight logic is correct, lock ordering is consistent, and the cancellation-safety guarantee is validated by tests.

The core invariants all hold: REFRESH_LOCKS serializes concurrent refreshes for the same credential file, the check-lock-check pattern in get_valid_chatgpt and force_refresh_if_access_token ensures waiters re-read the winner's persisted credential rather than issuing a redundant exchange, and the detached spawn correctly owns the OwnedMutexGuard through writeback so task cancellation cannot expose a half-rotated token. All new behavior paths are covered by wiremock-backed tests. The two P2 observations are clarifications rather than correctness defects.

No files require special attention — the changes are self-contained to auth/codex/auth.rs and the pool/inbound adapter helpers.

Important Files Changed

Filename Overview
src/auth/codex/auth.rs Core single-flight implementation: adds process-wide REFRESH_LOCKS registry keyed by canonical path, normalized_auth_path OnceCell caching, detached spawn for cancellation-safe refresh+writeback, and new force_refresh_if_access_token; overall design is sound with thorough test coverage
src/adapters/responses/pool.rs Adds chatgpt_access_token helper and updates force_refresh_or_cooldown to use force_refresh_if_access_token; outer state.accounts.refresh_lock is retained alongside the new inner path lock — consistent acquisition order avoids deadlock, but the dual-lock rationale is worth documenting
src/adapters/responses/inbound.rs One-liner change — threads the current credential into force_refresh_or_cooldown to enable forced-refresh coalescing on the inbound passthrough path

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant A as Caller A (get_valid_chatgpt)
    participant B as Caller B (get_valid_chatgpt)
    participant RL as REFRESH_LOCKS[path]
    participant Auth as Auth File
    participant UP as OAuth Upstream

    A->>Auth: read (before lock) → expired
    B->>Auth: read (before lock) → expired
    A->>RL: lock_owned() → acquired
    B->>RL: lock_owned() → blocks
    A->>Auth: read (after lock) → still expired
    A->>UP: spawn detached task: refresh_tokens()
    Note over A,UP: Task holds OwnedMutexGuard<()>
    UP-->>A: new access_token + refresh_token
    A->>Auth: write_refreshed_auth (atomic)
    A->>RL: drop guard (task complete)
    B->>RL: lock_owned() → acquired
    B->>Auth: read (after lock) → new token valid!
    B-->>B: return cached credential (no upstream call)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant A as Caller A (get_valid_chatgpt)
    participant B as Caller B (get_valid_chatgpt)
    participant RL as REFRESH_LOCKS[path]
    participant Auth as Auth File
    participant UP as OAuth Upstream

    A->>Auth: read (before lock) → expired
    B->>Auth: read (before lock) → expired
    A->>RL: lock_owned() → acquired
    B->>RL: lock_owned() → blocks
    A->>Auth: read (after lock) → still expired
    A->>UP: spawn detached task: refresh_tokens()
    Note over A,UP: Task holds OwnedMutexGuard<()>
    UP-->>A: new access_token + refresh_token
    A->>Auth: write_refreshed_auth (atomic)
    A->>RL: drop guard (task complete)
    B->>RL: lock_owned() → acquired
    B->>Auth: read (after lock) → new token valid!
    B-->>B: return cached credential (no upstream call)
Loading

Reviews (2): Last reviewed commit: "fix(codex): handle unexpected refresh cr..." | Re-trigger Greptile

Comment thread src/auth/codex/auth.rs Outdated
Comment thread src/adapters/responses/pool.rs Outdated
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a single-flight registry for ChatGPT OAuth token refreshes to serialize concurrent refreshes of the same credential file while allowing parallel refreshes for different accounts. It also adds robust tests to verify concurrent behavior, path normalization, and cancellation safety. The feedback points out a compilation error in normalized_auth_path where a closure is incorrectly passed to OnceCell::get_or_try_init instead of a Future directly, which must be resolved to compile successfully.

Comment thread src/auth/codex/auth.rs
@sonarqubecloud

Copy link
Copy Markdown

@amondnet
amondnet merged commit 6079053 into main Jul 15, 2026
16 checks passed
@amondnet
amondnet deleted the amondnet/perf-add-single-flight-to-codexauthstore-refresh branch July 15, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant