Skip to content

Conversation

@Saran33
Copy link

@Saran33 Saran33 commented Sep 22, 2025

Description

This PR fixes a critical concurrency issue in the HttpxHooks class that prevented concurrent 402 Payment Required responses from being processed correctly.

The issue was caused by a shared _is_retry flag that created a race condition - when multiple requests hit 402 responses concurrently, only the first request would perform the payment flow while all others would short-circuit and return 402 without attempting payment.

The fix replaces the shared instance variable with per-request state using httpx.Request.extensions, ensuring each request independently tracks its retry status without interfering with concurrent requests.

Key Changes:

  • Removed shared _is_retry instance variable from HttpxHooks class
  • Implemented per-request retry tracking using request.extensions["x402_is_retry"]
  • Each request now independently manages its payment retry state
  • Maintained infinite loop protection on a per-request basis

This enables critical use cases like:

  • AI agents processing large datasets with concurrent batch requests
  • Parallel data processing pipelines requiring multiple simultaneous paid API calls
  • High-throughput applications needing concurrent access to paid endpoints

Closes: #398

Tests

New Test Added

Added test_concurrent_payment_requests() that verifies 5 concurrent payment requests all succeed independently.

Test Results

All tests pass including the new concurrency test:

$ uv run python -m pytest tests/clients/test_httpx.py -v 
====================================================================== test session starts ======================================================================
platform darwin -- Python 3.10.16, pytest-8.4.0, pluggy-1.6.0 -- /x402/python/x402/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /x402/python/x402
configfile: pyproject.toml
plugins: anyio-4.9.0, asyncio-1.0.0
asyncio: mode=auto, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 10 items                                                                                                                                              

tests/clients/test_httpx.py::test_on_response_success PASSED                                                                                              [ 10%]
tests/clients/test_httpx.py::test_on_response_non_402 PASSED                                                                                              [ 20%]
tests/clients/test_httpx.py::test_on_response_retry PASSED                                                                                                [ 30%]
tests/clients/test_httpx.py::test_on_response_missing_request PASSED                                                                                      [ 40%]
tests/clients/test_httpx.py::test_on_response_payment_flow PASSED                                                                                         [ 50%]
tests/clients/test_httpx.py::test_on_response_payment_error PASSED                                                                                        [ 60%]
tests/clients/test_httpx.py::test_on_response_general_error PASSED                                                                                        [ 70%]
tests/clients/test_httpx.py::test_concurrent_payment_requests PASSED                                                                                      [ 80%]
tests/clients/test_httpx.py::test_x402_payment_hooks PASSED                                                                                               [ 90%]
tests/clients/test_httpx.py::test_x402_httpx_client PASSED                                                                                                [100%]

====================================================================== 10 passed in 0.34s =======================================================================

Updated Tests

  • Modified test_on_response_retry() to use per-request extensions instead of shared flag
  • Removed obsolete _is_retry flag assertions from error handling tests

Checklist

  • I have formatted and linted my code
    • Ran uv run ruff check src/x402/clients/httpx.py - All checks passed
  • All new and existing tests pass
    • All 10 tests pass including new concurrency test
  • My commits are signed (required for merge) -- you may need to rebase if you initially pushed unsigned commits

…try checking using `response.request.extensions.get("x402_is_retry")`
@cb-heimdall
Copy link

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 1
Sum 2

@vercel
Copy link

vercel bot commented Sep 22, 2025

@Saran33 is attempting to deploy a commit to the Coinbase Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

HttpxHooks Prevents Concurrent Payment Requests

2 participants