Skip to content

test: test_streaming_span_duration hangs >900s on slow CPU-only Ollama runner #1272

Description

@planetf1

Symptom

test/telemetry/test_tracing_backend.py::test_streaming_span_duration was killed by pytest-timeout (>900s) on the Python 3.13 CI runner in PR #1262. The 3.11 and 3.12 runners passed. Main branch CI passes consistently on all three Python versions, so this is intermittent rather than a deterministic 3.13 failure.

CI run: https://github.com/generative-computing/mellea/actions/runs/27607850677/job/81624294814

Diagnosis

Ollama topology

Each matrix job (3.11, 3.12, 3.13) starts its own isolated Ollama instance on its own ephemeral runner — there is no shared resource. The 3.13 runner logged:

WARNING: No NVIDIA/AMD GPU detected. Ollama will run in CPU-only mode.

All generation on that runner was CPU-bound.

Where it hangs

The test issues a real streaming generation request (ModelOption.STREAM: True) against Ollama. The 3.13 runner's CPU-only Ollama stalled mid-stream — chunks stopped arriving. The call stack at timeout was:

asyncio run_until_complete → run_forever → _run_once → selector.poll(timeout=-1)

timeout=-1 means the event loop had no ready callbacks and no timers — it was parked indefinitely on epoll waiting for I/O that never arrived. Tracing the path:

  1. OllamaModelBackend defaults timeout=None (ollama.py:91), so the underlying httpx client has no read timeout on the stream.
  2. The async queue feeder (async_helpers.py:40-47) is suspended on async for item in aresponse — Ollama stopped sending chunks.
  3. The feeder never reaches await aqueue.put(None) (the stream-end sentinel).
  4. astream() blocks forever on await self._async_queue.get() (core/base.py:660).
  5. avalue() loops calling astream() while not self._computed (core/base.py:592-593) — also blocked.
  6. drain_background_tasks() (line 308 of the test) was never reached.

The entire 900s budget was consumed waiting for Ollama to resume. The only thing that eventually unblocked the test was pytest-timeout's SIGALRM.

Why Python 3.13 specifically this time

This is coincidence, not a 3.13 asyncio incompatibility. The pattern (asyncio.Queue, create_task, async for over httpx) behaves identically on 3.11–3.13. The 3.13 runner drew a slower/more-loaded CPU runner, and CPU-only granite4.1:3b stalled under that load. The same hang will recur non-deterministically on whichever matrix leg gets an unlucky runner.

Timeline evidence

Time Event
10:03:38 test_multiple_generations_separate_spans PASSED
10:03:38 test_streaming_span_duration started
10:18:38 pytest-timeout SIGALRM fires — exactly 900s later

The other non-streaming tests in this file all passed because they use non-streaming generation, which completes or fails fast rather than stalling indefinitely.

Secondary finding: fixture thread leak

_reset_tracing_state() (test_tracing_backend.py:35-42) nulls the OTel provider globals without calling provider.shutdown(), leaking OtelBatchLogRecordProcessor background threads on each reset. 12+ leaked threads were visible in the timeout dump. These are idle daemons — not the cause of the hang — but worth cleaning up.

Root cause

OllamaModelBackend has no client-side read timeout on streaming responses. A mid-stream Ollama stall converts what should be a fast failure into an indefinite hang that can only be resolved by an external timeout.

Metadata

Metadata

Assignees

Labels

p2Medium/low: minor bugs, niche features, polish, docs, tests, cleanup. Scoped, lower urgency.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions