From c5c2dfe26f7d981c9a22dd1b0f53b98e89483f46 Mon Sep 17 00:00:00 2001 From: Faizan Saiyed Date: Fri, 2 Oct 2026 08:44:01 +0530 Subject: [PATCH 1/7] refactor(starlette): centralize active thread updates --- sentry_sdk/integrations/starlette.py | 33 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index a9ed8f2f73..6021d7f6e1 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -630,6 +630,24 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event": ) +def _update_active_thread() -> None: + client = sentry_sdk.get_client() + current_scope = sentry_sdk.get_current_scope() + + span_streaming = has_span_streaming_enabled(client.options) + if span_streaming: + current_span = current_scope.streamed_span + + if type(current_span) is StreamedSpan: + current_span._segment._update_active_thread() + elif current_scope.transaction is not None: + current_scope.transaction.update_active_thread() + + sentry_scope = sentry_sdk.get_isolation_scope() + if sentry_scope.profile is not None: + sentry_scope.profile.update_active_thread_id() + + def patch_request_response() -> None: old_request_response = starlette.routing.request_response @@ -654,20 +672,7 @@ def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any": if integration is None: return old_func(*args, **kwargs) - current_scope = sentry_sdk.get_current_scope() - - span_streaming = has_span_streaming_enabled(client.options) - if span_streaming: - current_span = current_scope.streamed_span - - if type(current_span) is StreamedSpan: - current_span._segment._update_active_thread() - elif current_scope.transaction is not None: - current_scope.transaction.update_active_thread() - - sentry_scope = sentry_sdk.get_isolation_scope() - if sentry_scope.profile is not None: - sentry_scope.profile.update_active_thread_id() + _update_active_thread() request = args[0] From a14cd78d40ffca3d8c102f1523d9f0ce0c1780d3 Mon Sep 17 00:00:00 2001 From: Faizan Saiyed Date: Fri, 2 Oct 2026 08:44:06 +0530 Subject: [PATCH 2/7] refactor(fastapi): use Starlette active thread helper --- sentry_sdk/integrations/fastapi.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 6a7c543c8c..6f0956ca06 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -21,6 +21,7 @@ StarletteIntegration, StarletteRequestExtractor, _get_cached_request_body_attribute, + _update_active_thread, ) except DidNotEnable: raise DidNotEnable("Starlette is not installed") @@ -195,22 +196,7 @@ def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any": @wraps(old_call) def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any": - current_scope = sentry_sdk.get_current_scope() - - client = sentry_sdk.get_client() - if has_span_streaming_enabled(client.options): - current_span = current_scope.streamed_span - - if type(current_span) is StreamedSpan: - segment = current_span._segment - segment._update_active_thread() - - elif current_scope.transaction is not None: - current_scope.transaction.update_active_thread() - - sentry_scope = sentry_sdk.get_isolation_scope() - if sentry_scope.profile is not None: - sentry_scope.profile.update_active_thread_id() + _update_active_thread() return old_call(*args, **kwargs) From 87d3a1309ac24bf4d67636e78cc6fa08ce571579 Mon Sep 17 00:00:00 2001 From: Faizan Saiyed Date: Fri, 2 Oct 2026 08:44:20 +0530 Subject: [PATCH 3/7] chore(fastapi): remove unused thread helper import --- sentry_sdk/integrations/fastapi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 6f0956ca06..9b35f59af7 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -8,7 +8,6 @@ from sentry_sdk.integrations import DidNotEnable from sentry_sdk.traces import StreamedSpan, get_current_span from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource -from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import has_data_collection_enabled, transaction_from_function if TYPE_CHECKING: From 7f93ff1cb2d2062417ad7cdea1f895665eb70c90 Mon Sep 17 00:00:00 2001 From: Faizan Saiyed Date: Fri, 2 Oct 2026 08:49:55 +0530 Subject: [PATCH 4/7] fix(starlette): preserve sync request scope access --- sentry_sdk/integrations/starlette.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 6021d7f6e1..fcf03d1b67 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -673,6 +673,8 @@ def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any": return old_func(*args, **kwargs) _update_active_thread() + current_scope = sentry_sdk.get_current_scope() + sentry_scope = sentry_sdk.get_isolation_scope() request = args[0] From 2a3964aacdebda81af9af5ee9d4bd693c7c2a1fc Mon Sep 17 00:00:00 2001 From: Faizan Saiyed Date: Fri, 2 Oct 2026 09:59:35 +0530 Subject: [PATCH 5/7] refactor(starlette): centralize sync thread wrapper --- sentry_sdk/integrations/starlette.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index fcf03d1b67..5de7f0ca09 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -648,6 +648,19 @@ def _update_active_thread() -> None: sentry_scope.profile.update_active_thread_id() +def _wrap_sync_handler(handler: "Callable[..., Any]") -> "Callable[..., Any]": + if getattr(handler, "_sentry_active_thread_is_patched", False): + return handler + + @functools.wraps(handler) + def _sentry_sync_handler(*args: "Any", **kwargs: "Any") -> "Any": + _update_active_thread() + return handler(*args, **kwargs) + + _sentry_sync_handler._sentry_active_thread_is_patched = True # type: ignore[attr-defined] + return _sentry_sync_handler + + def patch_request_response() -> None: old_request_response = starlette.routing.request_response From 387355219b25affaa7b43bbbbaa331b4de3d6899 Mon Sep 17 00:00:00 2001 From: Faizan Saiyed Date: Fri, 2 Oct 2026 09:59:43 +0530 Subject: [PATCH 6/7] refactor(fastapi): use Starlette sync thread wrapper --- sentry_sdk/integrations/fastapi.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 9b35f59af7..f8d96a4507 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -1,6 +1,5 @@ import sys from copy import deepcopy -from functools import wraps from typing import TYPE_CHECKING import sentry_sdk @@ -20,7 +19,7 @@ StarletteIntegration, StarletteRequestExtractor, _get_cached_request_body_attribute, - _update_active_thread, + _wrap_sync_handler, ) except DidNotEnable: raise DidNotEnable("Starlette is not installed") @@ -184,23 +183,8 @@ def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any": dependant and dependant.call is not None and not iscoroutinefunction(dependant.call) - # FastAPI >= 0.137 calls get_request_handler() on every request - # (router-tree traversal) rather than once at registration. Guard - # against accumulating _sentry_call wrappers on the shared - # dependant object, which would cause a RecursionError after ~987 - # requests as the call chain grows past Python's recursion limit. - and not getattr(dependant.call, "_sentry_is_patched", False) ): - old_call = dependant.call - - @wraps(old_call) - def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any": - _update_active_thread() - - return old_call(*args, **kwargs) - - _sentry_call._sentry_is_patched = True # type: ignore[attr-defined] - dependant.call = _sentry_call + dependant.call = _wrap_sync_handler(dependant.call) old_app = old_get_request_handler(*args, **kwargs) From 8409daf364de7ac8d4124c26ef00612074a33fb3 Mon Sep 17 00:00:00 2001 From: Faizan Saiyed Date: Tue, 6 Oct 2026 19:09:55 +0530 Subject: [PATCH 7/7] fix(starlette): use standard sentry patched attribute --- sentry_sdk/integrations/starlette.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 5de7f0ca09..f6988d096d 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -649,7 +649,7 @@ def _update_active_thread() -> None: def _wrap_sync_handler(handler: "Callable[..., Any]") -> "Callable[..., Any]": - if getattr(handler, "_sentry_active_thread_is_patched", False): + if getattr(handler, "_sentry_patched", False): return handler @functools.wraps(handler) @@ -657,7 +657,7 @@ def _sentry_sync_handler(*args: "Any", **kwargs: "Any") -> "Any": _update_active_thread() return handler(*args, **kwargs) - _sentry_sync_handler._sentry_active_thread_is_patched = True # type: ignore[attr-defined] + _sentry_sync_handler._sentry_patched = True # type: ignore[attr-defined] return _sentry_sync_handler