Jump to solution
Verify

The Fix

Fixes an infinite loop that can occur when using aiohttp in combination with async-solipsism by changing the condition from '<=' to '<'.

Based on closed aio-libs/aiohttp issue #10149 · PR/commit linked

Production note: This tends to surface only under concurrency. Reproduce with load tests and watch for lock contention/cancellation paths.

Jump to Verify Open PR/Commit
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@ +Fixed an infinite loop that can occur when using aiohttp in combination +with `async-solipsism`_ -- by :user:`bmerry`. +
repro.py
import asyncio import async_solipsism import pytest from aiohttp import web, test_utils @pytest.fixture def event_loop_policy(): return async_solipsism.EventLoopPolicy() @pytest.fixture(autouse=True) def mock_start_connection(monkeypatch): monkeypatch.setattr("aiohappyeyeballs.start_connection", async_solipsism.aiohappyeyeballs_start_connection) def socket_factory(host, port, family): return async_solipsism.ListenSocket((host, port)) async def test_integration(): app = web.Application() async with test_utils.TestServer(app, socket_factory=socket_factory) as server: async with test_utils.TestClient(server) as client: resp = await client.post("/hey", json={}) assert resp.status == 404 await asyncio.sleep(10000) resp = await client.post("/hey", json={}) assert resp.status == 404
verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
fix.md
Option A — Apply the official fix\nFixes an infinite loop that can occur when using aiohttp in combination with async-solipsism by changing the condition from '<=' to '<'.\nWhen NOT to use: This fix is not suitable for non-testing environments where real time is used.\n\n

Why This Fix Works in Production

  • Trigger: assert resp.status == 404
  • Mechanism: Fixes an infinite loop that can occur when using aiohttp in combination with async-solipsism by changing the condition from '<=' to '<'.
Production impact:
  • If left unfixed, failures can be intermittent under concurrency (hard to reproduce; shows up as sporadic 5xx/timeouts).

Why This Breaks in Prod

  • Shows up under Python 3.12.3 in real deployments (not just unit tests).
  • Production symptom (often without a traceback): assert resp.status == 404

Proof / Evidence

Discussion

High-signal excerpts from the issue thread (symptoms, repros, edge-cases).

“Changing it from <= to < is unlikely to have a material effect on production since its in microseconds and its likely below the resolution…”
@bdraco · 2024-12-09 · source
“> Changing it from <= to < is unlikely to have a material effect on production since its in microseconds and its likely below the…”
@bmerry · 2024-12-09 · source

Failure Signature (Search String)

  • assert resp.status == 404
  • 4. Run pytest. The test hangs.
Copy-friendly signature
signature.txt
Failure Signature ----------------- assert resp.status == 404 4. Run pytest. The test hangs.

Error Message

Signature-only (no traceback captured)
error.txt
Error Message ------------- assert resp.status == 404 4. Run pytest. The test hangs.

Minimal Reproduction

repro.py
import asyncio import async_solipsism import pytest from aiohttp import web, test_utils @pytest.fixture def event_loop_policy(): return async_solipsism.EventLoopPolicy() @pytest.fixture(autouse=True) def mock_start_connection(monkeypatch): monkeypatch.setattr("aiohappyeyeballs.start_connection", async_solipsism.aiohappyeyeballs_start_connection) def socket_factory(host, port, family): return async_solipsism.ListenSocket((host, port)) async def test_integration(): app = web.Application() async with test_utils.TestServer(app, socket_factory=socket_factory) as server: async with test_utils.TestClient(server) as client: resp = await client.post("/hey", json={}) assert resp.status == 404 await asyncio.sleep(10000) resp = await client.post("/hey", json={}) assert resp.status == 404

Environment

  • Python: 3.12.3

What Broke

Tests using async_solipsism can get stuck in an infinite loop, causing timeouts.

Fix Options (Details)

Option A — Apply the official fix

Fixes an infinite loop that can occur when using aiohttp in combination with async-solipsism by changing the condition from '<=' to '<'.

When NOT to use: This fix is not suitable for non-testing environments where real time is used.

Option D — Guard side-effects with OnceOnly Guardrail for side-effects

Mitigate duplicate external side-effects under retries/timeouts/agent loops by gating the operation before calling external systems.

  • Place OnceOnly between your code/agent and real side-effects (Stripe, emails, CRM, APIs).
  • Use a stable key per side-effect (e.g., customer_id + action + idempotency_key).
  • Fail-safe: configure fail-open vs fail-closed based on blast radius and spend risk.
Show example snippet (optional)
onceonly.py
from onceonly import OnceOnly import os once = OnceOnly(api_key=os.environ["ONCEONLY_API_KEY"], fail_open=True) # Stable idempotency key per real side-effect. # Use a request id / job id / webhook delivery id / Stripe event id, etc. event_id = "evt_..." # replace key = f"stripe:webhook:{event_id}" res = once.check_lock(key=key, ttl=3600) if res.duplicate: return {"status": "already_processed"} # Safe to execute the side-effect exactly once. handle_event(event_id)

See OnceOnly SDK

When NOT to use: Do not use this to hide logic bugs or data corruption. Use it to block duplicate external side-effects and enforce tool permissions/spend caps.

Fix reference: https://github.com/aio-libs/aiohttp/pull/10151

Last verified: 2026-02-09. Validate in your environment.

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • This fix is not suitable for non-testing environments where real time is used.
  • Do not use this to hide logic bugs or data corruption. Use it to block duplicate external side-effects and enforce tool permissions/spend caps.

Verify Fix

verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.

Did This Fix Work in Your Case?

Quick signal helps us prioritize which fixes to verify and improve.

Prevention

  • Add a stress test that runs high-concurrency workloads and fails on thread dumps / blocked locks.
  • Enable watchdog dumps in prod (faulthandler, thread dump endpoint) to capture deadlocks quickly.
  • Make timeouts explicit and test them (unit + integration) to avoid silent behavior changes.
  • Instrument retries (attempt count + reason) and alert on spikes to catch dependency slowdowns.

Related Issues

No related fixes found.

Sources

We don’t republish the full GitHub discussion text. Use the links above for context.