@@ -268,6 +268,83 @@ async def test_stream_unary(grpc_server_and_channel):
268268 assert response .text == "test"
269269
270270
271+ @pytest .mark .asyncio
272+ async def test_concurrent_requests_do_not_leak_method_name (sentry_init , capture_events ):
273+ """
274+ Regression test for SDK-1184: the async ServerInterceptor must not stash
275+ request-specific state on the shared interceptor instance, otherwise
276+ concurrent requests can overwrite each other's method names.
277+ """
278+ from sentry_sdk .integrations .grpc .aio .server import ServerInterceptor
279+
280+ sentry_init (traces_sample_rate = 1.0 , integrations = [GRPCIntegration ()])
281+ events = capture_events ()
282+
283+ interceptor = ServerInterceptor ()
284+
285+ request_a_can_finish = asyncio .Event ()
286+ request_a_started = asyncio .Event ()
287+
288+ async def handler_a (request , context ):
289+ request_a_started .set ()
290+ await request_a_can_finish .wait ()
291+ return "a-response"
292+
293+ async def handler_b (request , context ):
294+ return "b-response"
295+
296+ def make_handler (behavior ):
297+ h = type (
298+ "H" ,
299+ (),
300+ {
301+ "request_streaming" : False ,
302+ "response_streaming" : False ,
303+ "unary_unary" : behavior ,
304+ "request_deserializer" : None ,
305+ "response_serializer" : None ,
306+ },
307+ )()
308+ return h
309+
310+ class FakeHandlerCallDetails :
311+ def __init__ (self , method ):
312+ self .method = method
313+
314+ class FakeContext :
315+ def invocation_metadata (self ):
316+ return ()
317+
318+ async def continuation_a (_ ):
319+ return make_handler (handler_a )
320+
321+ async def continuation_b (_ ):
322+ return make_handler (handler_b )
323+
324+ wrapped_a_handler = await interceptor .intercept_service (
325+ continuation_a , FakeHandlerCallDetails ("/svc/MethodA" )
326+ )
327+ # Kick off request A; it parks inside the handler awaiting the event.
328+ task_a = asyncio .create_task (
329+ wrapped_a_handler .unary_unary (None , FakeContext ()) # type: ignore[attr-defined]
330+ )
331+ await request_a_started .wait ()
332+
333+ # While A is parked, intercept B. Pre-fix this overwrote shared state on
334+ # the interceptor and A would later read B's method name.
335+ wrapped_b_handler = await interceptor .intercept_service (
336+ continuation_b , FakeHandlerCallDetails ("/svc/MethodB" )
337+ )
338+ await wrapped_b_handler .unary_unary (None , FakeContext ()) # type: ignore[attr-defined]
339+
340+ request_a_can_finish .set ()
341+ await task_a
342+
343+ transactions = [e for e in events if e .get ("type" ) == "transaction" ]
344+ names = sorted (t ["transaction" ] for t in transactions )
345+ assert names == ["/svc/MethodA" , "/svc/MethodB" ]
346+
347+
271348@pytest .mark .asyncio
272349async def test_span_origin (grpc_server_and_channel , capture_events_forksafe ):
273350 _ , channel = grpc_server_and_channel
0 commit comments