Skip to content

Conversation

@codefromthecrypt
Copy link

@codefromthecrypt codefromthecrypt commented Jan 14, 2026

Motivation and Context

In integrating latest llama-stack with MCP examples via OpenAI Responses API, I ran into a couple glitches which broke requests. I narrowed them down to GeneratorExit handling. When rebuilding llama-stack against this branch all scenarios pass.

I noticed this also fixes #1214 which was closed due to missing tests. This adds them.

The Problem

Scenario 1: GC cleanup leaks GeneratorExit

When garbage collector cleans up an orphaned generator while a background task has failed:

BaseExceptionGroup: unhandled errors in a TaskGroup (2 sub-exceptions)
├─ HTTPStatusError: Client error '401 Unauthorized'
└─ GeneratorExit  ← leaked from GC calling aclose()

Background:

Scenario 2: User TaskGroup wraps GeneratorExit

When user code has a generator yielding inside a TaskGroup:

BaseExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
└─ GeneratorExit  ← wrapped by user's TaskGroup during cleanup

Background:

The Fix

Two exception handlers at the yield point handle both scenarios:

┌─────────────────────────────────────────────────────────────────┐
│                        yield streams                             │
└──────────────────────────────┬──────────────────────────────────┘
                               │
           ┌───────────────────┴───────────────────┐
           │                                       │
           ▼                                       ▼
┌─────────────────────────┐          ┌─────────────────────────────┐
│  Branch 1               │          │  Branch 2                    │
│  except GeneratorExit   │          │  except BaseExceptionGroup   │
│  → suppress             │          │  → split(GeneratorExit)      │
│                         │          │  → re-raise rest             │
└─────────────────────────┘          └─────────────────────────────┘
        │                                       │
        ▼                                       ▼
   Scenario 1:                         Scenario 2:
   GC calls aclose()                   User TaskGroup wraps
   on suspended generator              GeneratorExit

Changes

  • Added GeneratorExit and BaseExceptionGroup handling in sse_client and streamable_http_client
  • Added exceptiongroup>=1.0.0 as conditional dependency for Python 3.10 support
  • Fixed stream cleanup: closes read_stream in finally block

How Has This Been Tested?

Parameterized tests in tests/client/test_resource_cleanup.py (each runs for both SSE and Streamable HTTP):

  • test_generator_exit_on_gc_cleanup[sse/streamable] - Scenario 1: GC cleanup
  • test_generator_exit_in_exception_group[sse/streamable] - Scenario 2: BaseExceptionGroup([GeneratorExit])
  • test_generator_exit_mixed_group[sse/streamable] - Scenario 2: BaseExceptionGroup([GeneratorExit, ValueError])

Breaking Changes

None

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling

@codefromthecrypt codefromthecrypt force-pushed the fix/generator-exit-cleanup branch 7 times, most recently from 173f35e to d3a38f9 Compare January 15, 2026 01:57
@codefromthecrypt codefromthecrypt marked this pull request as ready for review January 15, 2026 02:01
@codefromthecrypt
Copy link
Author

codefromthecrypt commented Jan 15, 2026

@Kludex I ran into this with llama-stack and verify no errors with this patch. if I'm addressing this the wrong way, lemme know.

Screenshot 2026-01-15 at 11 13 23 AM

@codefromthecrypt codefromthecrypt marked this pull request as draft January 15, 2026 02:17
@codefromthecrypt codefromthecrypt force-pushed the fix/generator-exit-cleanup branch 3 times, most recently from 932f052 to ecf1234 Compare January 15, 2026 07:55
GeneratorExit can leak from sse_client and streamablehttp_client
during cleanup, causing RuntimeError in downstream code. This handles
both direct GeneratorExit and BaseExceptionGroup wrapping (cpython#95571).

Fixes modelcontextprotocol#1214

Signed-off-by: Adrian Cole <adrian@tetrate.io>
@codefromthecrypt codefromthecrypt force-pushed the fix/generator-exit-cleanup branch from ecf1234 to bce6dd2 Compare January 15, 2026 08:18
@codefromthecrypt codefromthecrypt marked this pull request as ready for review January 15, 2026 08:22
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.

Unhandled GeneratorExit error

1 participant