Skip to content

Commit b0885b8

Browse files
fix(grpc): Derive interception state from channel fields (#5302)
Detect existing `ClientInterceptor` by inspecting `grpc.Channel`. Replace integration-level flag with a helper that walks the `grpc.Channel` object to determine if an instance of `ClientInterceptor` has already been attached. Closes #5183
1 parent b5e7e05 commit b0885b8

File tree

3 files changed

+55
-5
lines changed

3 files changed

+55
-5
lines changed

sentry_sdk/integrations/grpc/__init__.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,29 @@ def __getitem__(self, _):
5050
GRPC_VERSION = parse_version(grpc.__version__)
5151

5252

53+
def _is_channel_intercepted(channel: "Channel") -> bool:
54+
interceptor = getattr(channel, "_interceptor", None)
55+
while interceptor is not None:
56+
if isinstance(interceptor, ClientInterceptor):
57+
return True
58+
59+
inner_channel = getattr(channel, "_channel", None)
60+
if inner_channel is None:
61+
return False
62+
63+
channel = inner_channel
64+
interceptor = getattr(channel, "_interceptor", None)
65+
66+
return False
67+
68+
5369
def _wrap_channel_sync(func: "Callable[P, Channel]") -> "Callable[P, Channel]":
5470
"Wrapper for synchronous secure and insecure channel."
5571

5672
@wraps(func)
5773
def patched_channel(*args: "Any", **kwargs: "Any") -> "Channel":
5874
channel = func(*args, **kwargs)
59-
if not ClientInterceptor._is_intercepted:
60-
ClientInterceptor._is_intercepted = True
75+
if not _is_channel_intercepted(channel):
6176
return intercept_channel(channel, ClientInterceptor())
6277
else:
6378
return channel
@@ -70,7 +85,7 @@ def _wrap_intercept_channel(func: "Callable[P, Channel]") -> "Callable[P, Channe
7085
def patched_intercept_channel(
7186
channel: "Channel", *interceptors: "grpc.ServerInterceptor"
7287
) -> "Channel":
73-
if ClientInterceptor._is_intercepted:
88+
if _is_channel_intercepted(channel):
7489
interceptors = tuple(
7590
[
7691
interceptor

sentry_sdk/integrations/grpc/client.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ class ClientInterceptor(
2222
grpc.UnaryUnaryClientInterceptor, # type: ignore
2323
grpc.UnaryStreamClientInterceptor, # type: ignore
2424
):
25-
_is_intercepted = False
26-
2725
def intercept_unary_unary(
2826
self: "ClientInterceptor",
2927
continuation: "Callable[[ClientCallDetails, Message], _UnaryOutcome]",

tests/integrations/grpc/test_grpc.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from sentry_sdk import start_span, start_transaction
99
from sentry_sdk.consts import OP
1010
from sentry_sdk.integrations.grpc import GRPCIntegration
11+
from sentry_sdk.integrations.grpc.client import ClientInterceptor
1112
from tests.conftest import ApproxDict
1213
from tests.integrations.grpc.grpc_test_service_pb2 import gRPCTestMessage
1314
from tests.integrations.grpc.grpc_test_service_pb2_grpc import (
@@ -269,6 +270,42 @@ def test_grpc_client_other_interceptor(sentry_init, capture_events_forksafe):
269270
)
270271

271272

273+
@pytest.mark.forked
274+
def test_prevent_dual_client_interceptor(sentry_init, capture_events_forksafe):
275+
sentry_init(traces_sample_rate=1.0, integrations=[GRPCIntegration()])
276+
events = capture_events_forksafe()
277+
278+
server, channel = _set_up()
279+
280+
# Intercept the channel
281+
channel = grpc.intercept_channel(channel, ClientInterceptor())
282+
stub = gRPCTestServiceStub(channel)
283+
284+
with start_transaction():
285+
stub.TestServe(gRPCTestMessage(text="test"))
286+
287+
_tear_down(server=server)
288+
289+
events.write_file.close()
290+
events.read_event()
291+
local_transaction = events.read_event()
292+
span = local_transaction["spans"][0]
293+
294+
assert len(local_transaction["spans"]) == 1
295+
assert span["op"] == OP.GRPC_CLIENT
296+
assert (
297+
span["description"]
298+
== "unary unary call to /grpc_test_server.gRPCTestService/TestServe"
299+
)
300+
assert span["data"] == ApproxDict(
301+
{
302+
"type": "unary unary",
303+
"method": "/grpc_test_server.gRPCTestService/TestServe",
304+
"code": "OK",
305+
}
306+
)
307+
308+
272309
@pytest.mark.forked
273310
def test_grpc_client_and_servers_interceptors_integration(
274311
sentry_init, capture_events_forksafe

0 commit comments

Comments
 (0)