Skip to content

Fix gRPC client interceptor breaking bidirectional streaming (#1180)#4259

Open
juandelacruz-calvo wants to merge 1 commit intoopen-telemetry:mainfrom
juandelacruz-calvo:fix/grpc-bidi-stream-1180
Open

Fix gRPC client interceptor breaking bidirectional streaming (#1180)#4259
juandelacruz-calvo wants to merge 1 commit intoopen-telemetry:mainfrom
juandelacruz-calvo:fix/grpc-bidi-stream-1180

Conversation

@juandelacruz-calvo
Copy link

Summary

Root Cause

In _client.py, intercept_stream() routed all RPCs with is_server_stream=True into _intercept_server_stream(), including bidirectional streams (where both is_client_stream and is_server_stream are True). _intercept_server_stream is a generator function (uses yield from), so calling it returns a bare Python generator that lacks the grpc.Call interface (add_done_callback, cancel, is_active, etc.).

Downstream code — notably google.api_core.bidi.BidiRpc.open() used by Google Cloud Pub/Sub — expects a real gRPC call object and crashes:

AttributeError: 'generator' object has no attribute 'add_done_callback'

The Fix

One condition change in intercept_stream():

# Before:
if client_info.is_server_stream:

# After:
if client_info.is_server_stream and not client_info.is_client_stream:

This ensures only unary-stream RPCs use the generator-based _intercept_server_stream. Bidi streams fall through to _intercept, which preserves the grpc.Call/grpc.Future interface.

RPC Type Before After
Unary-Stream _intercept_server_stream _intercept_server_stream (unchanged)
Stream-Unary _intercept _intercept (unchanged)
Bidi (Stream-Stream) _intercept_server_stream (broken) _intercept (fixed)

Files Changed

File Change
instrumentation/.../grpc/_client.py One-line condition fix
instrumentation/.../tests/test_client_interceptor.py Add test_stream_stream_preserves_call_interface
CHANGELOG.md Add bugfix entry under Unreleased/Fixed

Test plan

  • Existing test_stream_stream continues to pass (span correctness)
  • New test_stream_stream_preserves_call_interface passes — verifies grpc.Call attributes on bidi response
  • Existing unary-stream tests pass (no regression from condition change)
  • Existing stream-unary tests pass

Made with Cursor

…lemetry#1180)

Route bidi (stream-stream) RPCs through `_intercept` instead of the
generator-based `_intercept_server_stream`. The generator wrapper strips
the grpc.Call/grpc.Future interface, causing downstream code (e.g.
google.api_core.bidi.BidiRpc) to crash with:

    AttributeError: 'generator' object has no attribute 'add_done_callback'

The fix adds `and not client_info.is_client_stream` to the condition in
`intercept_stream()` so only unary-stream RPCs use the generator path.

Includes a regression test verifying the bidi stream response preserves
the grpc.Call interface (add_done_callback, cancel, is_active).

Co-authored-by: Cursor <cursoragent@cursor.com>
@juandelacruz-calvo
Copy link
Author

@xrmx

I have created an even simpler PR to fix the issues with PubSub that hopefully should be simpler to get approval for 🙏

@juandelacruz-calvo
Copy link
Author

juandelacruz-calvo commented Feb 25, 2026

For anyone needing a fix, this monkey patch should do:

    from opentelemetry.instrumentation.grpc._client import OpenTelemetryClientInterceptor

    _original_intercept_stream = OpenTelemetryClientInterceptor.intercept_stream

    def _patched_intercept_stream(self, request_or_iterator, metadata, client_info, invoker):
        if client_info.is_server_stream and client_info.is_client_stream:
            return self._intercept(request_or_iterator, metadata, client_info, invoker)
        return _original_intercept_stream(self, request_or_iterator, metadata, client_info, invoker)

    OpenTelemetryClientInterceptor.intercept_stream = _patched_intercept_stream

@tammy-baylis-swi
Copy link
Contributor

Thanks for contributing! Please could you make sure the existing tests all still pass or are adjusted. For example when tox -e py313-test-instrumentation-grpc-1 there are 2 failures at status code asserts.

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

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

2 participants