From a5f7afe1a6cf4b3b91ba9fe1469b0d688949587f Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Tue, 13 Feb 2024 18:09:01 +0100 Subject: [PATCH 01/17] trying something out --- sentry_sdk/hub.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 45afb56cc9..b4a4e434cc 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -1,8 +1,12 @@ import copy import sys - from contextlib import contextmanager +try: + from os import register_at_fork +except ImportError: + register_at_fork = None + from sentry_sdk._compat import with_metaclass from sentry_sdk.consts import INSTRUMENTER from sentry_sdk.scope import Scope @@ -700,3 +704,13 @@ def trace_propagation_meta(self, span=None): GLOBAL_HUB = Hub() _local.set(GLOBAL_HUB) + + +if register_at_fork is not None: + + def detect_parent_with_live_threads(): + import threading + + print("ACTIVE THREADS IN PARENT:", threading.active_count()) + + register_at_fork(before=detect_parent_with_live_threads) From 2bc4c31024cc02ad56010af26a1a77755f7862f9 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 14:38:59 +0100 Subject: [PATCH 02/17] no before fork hook in uwsgi, trying something else --- sentry_sdk/_compat.py | 9 +++++++++ sentry_sdk/hub.py | 15 --------------- sentry_sdk/profiler.py | 4 +++- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index 8c1bf9711f..93e76bdecf 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -140,6 +140,15 @@ def __new__(metacls, name, this_bases, d): return type.__new__(MetaClass, "temporary_class", (), {}) +def check_profiler_support(): + try: + from uwsgi import opt + except ImportError: + return + + print(opt) + + def check_thread_support(): # type: () -> None try: diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index b4a4e434cc..21b59283aa 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -2,11 +2,6 @@ import sys from contextlib import contextmanager -try: - from os import register_at_fork -except ImportError: - register_at_fork = None - from sentry_sdk._compat import with_metaclass from sentry_sdk.consts import INSTRUMENTER from sentry_sdk.scope import Scope @@ -704,13 +699,3 @@ def trace_propagation_meta(self, span=None): GLOBAL_HUB = Hub() _local.set(GLOBAL_HUB) - - -if register_at_fork is not None: - - def detect_parent_with_live_threads(): - import threading - - print("ACTIVE THREADS IN PARENT:", threading.active_count()) - - register_at_fork(before=detect_parent_with_live_threads) diff --git a/sentry_sdk/profiler.py b/sentry_sdk/profiler.py index be954b2a2c..962aba4d53 100644 --- a/sentry_sdk/profiler.py +++ b/sentry_sdk/profiler.py @@ -36,7 +36,7 @@ from collections import deque import sentry_sdk -from sentry_sdk._compat import PY33, PY311 +from sentry_sdk._compat import PY33, PY311, check_profiler_support from sentry_sdk._lru_cache import LRUCache from sentry_sdk._types import TYPE_CHECKING from sentry_sdk.utils import ( @@ -193,6 +193,8 @@ def setup_profiler(options): logger.warn("[Profiling] Profiler requires Python >= 3.3") return False + check_profiler_support() + frequency = DEFAULT_SAMPLING_FREQUENCY if is_gevent(): From 092fed2c95c1905c80bf194d3c3f2d43d3301196 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 14:44:30 +0100 Subject: [PATCH 03/17] wip --- sentry_sdk/_compat.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index 93e76bdecf..00bde00761 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -148,6 +148,16 @@ def check_profiler_support(): print(opt) + from warnings import warn + + warn( + Warning( + "We detected the use of uWSGI in preforking mode. " + "This might lead to issues with the profiler. " + 'Please run uWSGI with the "--lazy-apps" flag.' + ) + ) + def check_thread_support(): # type: () -> None From 1985b60dec4583a7f7aad6b3692736ec5f3f9216 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 15:38:16 +0100 Subject: [PATCH 04/17] disable profiling --- sentry_sdk/_compat.py | 35 +++++++++++++++++++++++++++++++---- sentry_sdk/profiler.py | 4 +++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index 00bde00761..308ac7de22 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -141,23 +141,50 @@ def __new__(metacls, name, this_bases, d): def check_profiler_support(): + # type: () -> None + # If uWSGI is running in preforking mode (default) and the SDK spawns a + # background thread on startup, i.e., before the process is forked, this + # can lead to segfaults in the forked workers: + # https://github.com/getsentry/sentry-python/issues/2699 + # We usually don't spawn threads right on startup but rather on demand, but + # if someone e.g. emits some metrics at startup manually, we will create a + # thread before the process is forked. + # + # We've tracked the segfaults down to the use of `sys._current_frames()` in + # the profiler, though there might be more causes, so we might need this sort + # of check elsewhere as well. It seems like after the fork, something + # related to the originally active threads hasn't been cleaned up properly in + # the child processes and this can make them segfault. + # + # In Python 3.12, forking a process with live threads even issues + # a `DeprecationWarning`, so this is something that is generally discouraged. + # + # Here we check whether uWSGI is running in preforking mode and if so, we + # disable the profiler and issue a warning to switch to loading the app in + # each worker separately (with `--lazy-apps` or `--lazy`). + # https://uwsgi-docs.readthedocs.io/en/latest/articles/TheArtOfGracefulReloading.html#preforking-vs-lazy-apps-vs-lazy try: from uwsgi import opt except ImportError: - return + return True - print(opt) + if opt.get("--lazy-apps") or opt.get("--lazy"): + # We're not running in preforking mode, nothing to do. + return True from warnings import warn warn( Warning( "We detected the use of uWSGI in preforking mode. " - "This might lead to issues with the profiler. " - 'Please run uWSGI with the "--lazy-apps" flag.' + "This might lead to issues with the workers when profiling is active. " + "Disabling profiling. " + 'Please run uWSGI with the "--lazy-apps" flag to enable it.' ) ) + return False + def check_thread_support(): # type: () -> None diff --git a/sentry_sdk/profiler.py b/sentry_sdk/profiler.py index 962aba4d53..96f45fa6a9 100644 --- a/sentry_sdk/profiler.py +++ b/sentry_sdk/profiler.py @@ -193,7 +193,9 @@ def setup_profiler(options): logger.warn("[Profiling] Profiler requires Python >= 3.3") return False - check_profiler_support() + supported = check_profiler_support() + if not supported: + raise RuntimeError("Incompatible uWSGI mode.") frequency = DEFAULT_SAMPLING_FREQUENCY From e045715e798ea2a96e4da73455b3c2523d183c9c Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 16:51:50 +0100 Subject: [PATCH 05/17] Issue a warning --- sentry_sdk/_compat.py | 80 ++++++++++++++++++++++--------------------- sentry_sdk/hub.py | 3 +- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index 308ac7de22..e2ccbd49ca 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -140,28 +140,58 @@ def __new__(metacls, name, this_bases, d): return type.__new__(MetaClass, "temporary_class", (), {}) -def check_profiler_support(): +def check_thread_support(): + # type: () -> None + try: + from uwsgi import opt # type: ignore + except ImportError: + return + + # When `threads` is passed in as a uwsgi option, + # `enable-threads` is implied on. + if "threads" in opt: + return + + # put here because of circular import + from sentry_sdk.consts import FALSE_VALUES + + if str(opt.get("enable-threads", "0")).lower() in FALSE_VALUES: + from warnings import warn + + warn( + Warning( + "We detected the use of uwsgi with disabled threads. " + "This will cause issues with the transport you are " + "trying to use. Please enable threading for uwsgi. " + '(Add the "enable-threads" flag).' + ) + ) + + +def check_uwsgi_support(): # type: () -> None # If uWSGI is running in preforking mode (default) and the SDK spawns a # background thread on startup, i.e., before the process is forked, this # can lead to segfaults in the forked workers: # https://github.com/getsentry/sentry-python/issues/2699 - # We usually don't spawn threads right on startup but rather on demand, but + # We usually don't spawn threads right away but rather on demand, but # if someone e.g. emits some metrics at startup manually, we will create a # thread before the process is forked. # # We've tracked the segfaults down to the use of `sys._current_frames()` in - # the profiler, though there might be more causes, so we might need this sort - # of check elsewhere as well. It seems like after the fork, something - # related to the originally active threads hasn't been cleaned up properly in - # the child processes and this can make them segfault. + # the profiler when called from a child, though there might be more causes + # (gc hitting the same bad references?). + # + # It seems like after the fork, something related to the originally active + # threads doesn't get cleaned up properly in the child processes and this can + # make them segfault. # # In Python 3.12, forking a process with live threads even issues # a `DeprecationWarning`, so this is something that is generally discouraged. # # Here we check whether uWSGI is running in preforking mode and if so, we - # disable the profiler and issue a warning to switch to loading the app in - # each worker separately (with `--lazy-apps` or `--lazy`). + # issue a warning to switch to loading the app in each worker separately + # (with `--lazy-apps` or `--lazy`). # https://uwsgi-docs.readthedocs.io/en/latest/articles/TheArtOfGracefulReloading.html#preforking-vs-lazy-apps-vs-lazy try: from uwsgi import opt @@ -177,38 +207,10 @@ def check_profiler_support(): warn( Warning( "We detected the use of uWSGI in preforking mode. " - "This might lead to issues with the workers when profiling is active. " - "Disabling profiling. " - 'Please run uWSGI with the "--lazy-apps" flag to enable it.' + "This might lead to issues with workers when a background thread " + "(e.g. profiler) is active before the process is forked. " + 'Please run uWSGI with the "--lazy-apps" flag for full support.' ) ) return False - - -def check_thread_support(): - # type: () -> None - try: - from uwsgi import opt # type: ignore - except ImportError: - return - - # When `threads` is passed in as a uwsgi option, - # `enable-threads` is implied on. - if "threads" in opt: - return - - # put here because of circular import - from sentry_sdk.consts import FALSE_VALUES - - if str(opt.get("enable-threads", "0")).lower() in FALSE_VALUES: - from warnings import warn - - warn( - Warning( - "We detected the use of uwsgi with disabled threads. " - "This will cause issues with the transport you are " - "trying to use. Please enable threading for uwsgi. " - '(Add the "enable-threads" flag).' - ) - ) diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 21b59283aa..323f03b506 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -2,7 +2,7 @@ import sys from contextlib import contextmanager -from sentry_sdk._compat import with_metaclass +from sentry_sdk._compat import check_uwsgi_support, with_metaclass from sentry_sdk.consts import INSTRUMENTER from sentry_sdk.scope import Scope from sentry_sdk.client import Client @@ -102,6 +102,7 @@ def _init(*args, **kwargs): client = Client(*args, **kwargs) # type: ignore Hub.current.bind_client(client) _check_python_deprecations() + check_uwsgi_support() rv = _InitGuard(client) return rv From b47589d844515abd0d9b458395105c0e0872bc17 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 17:08:12 +0100 Subject: [PATCH 06/17] remove old stuff --- sentry_sdk/profiler.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sentry_sdk/profiler.py b/sentry_sdk/profiler.py index 96f45fa6a9..be954b2a2c 100644 --- a/sentry_sdk/profiler.py +++ b/sentry_sdk/profiler.py @@ -36,7 +36,7 @@ from collections import deque import sentry_sdk -from sentry_sdk._compat import PY33, PY311, check_profiler_support +from sentry_sdk._compat import PY33, PY311 from sentry_sdk._lru_cache import LRUCache from sentry_sdk._types import TYPE_CHECKING from sentry_sdk.utils import ( @@ -193,10 +193,6 @@ def setup_profiler(options): logger.warn("[Profiling] Profiler requires Python >= 3.3") return False - supported = check_profiler_support() - if not supported: - raise RuntimeError("Incompatible uWSGI mode.") - frequency = DEFAULT_SAMPLING_FREQUENCY if is_gevent(): From ce570fd008518e77436a2bc4a8f18c3514345eb3 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 19:44:30 +0100 Subject: [PATCH 07/17] wip --- sentry_sdk/_compat.py | 89 ++++++++++++++++--------------------------- sentry_sdk/client.py | 51 +++++++++++++------------ sentry_sdk/consts.py | 1 - sentry_sdk/hub.py | 3 +- sentry_sdk/worker.py | 2 - 5 files changed, 61 insertions(+), 85 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index e2ccbd49ca..3d52d610fb 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -140,77 +140,54 @@ def __new__(metacls, name, this_bases, d): return type.__new__(MetaClass, "temporary_class", (), {}) -def check_thread_support(): - # type: () -> None +def check_uwsgi_thread_support(): + # type: () -> bool + # We check two things here: + # + # 1. uWSGI doesn't run in threaded mode by default -- issue a warning if + # that's the case. + # + # 2. Additionally, if uWSGI is running in preforking mode (default), it needs + # the --py-call-uwsgi-fork-hooks option for the SDK to work properly. This + # is because any background threads spawned before the main process is + # forked are NOT CLEANED UP IN THE CHILDREN BY DEFAULT even if + # --enable-threads is on. One has to also explicitly provide + # --py-call-uwsgi-fork-hooks to force uWSGI to run regular cpython + # after-fork hooks that take care of cleaning up stale thread data. try: from uwsgi import opt # type: ignore except ImportError: - return + return True # When `threads` is passed in as a uwsgi option, # `enable-threads` is implied on. - if "threads" in opt: - return + threads_enabled = bool("threads" in opt or opt.get("enable-threads")) + fork_hooks_on = bool(opt.get("py-call-uwsgi-fork-hooks")) + lazy_mode = bool(opt.get("lazy-apps") or opt.get("lazy")) - # put here because of circular import - from sentry_sdk.consts import FALSE_VALUES - - if str(opt.get("enable-threads", "0")).lower() in FALSE_VALUES: + if lazy_mode and not threads_enabled: from warnings import warn warn( Warning( - "We detected the use of uwsgi with disabled threads. " - "This will cause issues with the transport you are " - "trying to use. Please enable threading for uwsgi. " - '(Add the "enable-threads" flag).' + "We detected the use of uWSGI without thread support. " + "This might lead to unexpected issues with the Sentry SDK. " + 'Please run uWSGI with the "--enable-threads" flag for full support.' ) ) + return False -def check_uwsgi_support(): - # type: () -> None - # If uWSGI is running in preforking mode (default) and the SDK spawns a - # background thread on startup, i.e., before the process is forked, this - # can lead to segfaults in the forked workers: - # https://github.com/getsentry/sentry-python/issues/2699 - # We usually don't spawn threads right away but rather on demand, but - # if someone e.g. emits some metrics at startup manually, we will create a - # thread before the process is forked. - # - # We've tracked the segfaults down to the use of `sys._current_frames()` in - # the profiler when called from a child, though there might be more causes - # (gc hitting the same bad references?). - # - # It seems like after the fork, something related to the originally active - # threads doesn't get cleaned up properly in the child processes and this can - # make them segfault. - # - # In Python 3.12, forking a process with live threads even issues - # a `DeprecationWarning`, so this is something that is generally discouraged. - # - # Here we check whether uWSGI is running in preforking mode and if so, we - # issue a warning to switch to loading the app in each worker separately - # (with `--lazy-apps` or `--lazy`). - # https://uwsgi-docs.readthedocs.io/en/latest/articles/TheArtOfGracefulReloading.html#preforking-vs-lazy-apps-vs-lazy - try: - from uwsgi import opt - except ImportError: - return True - - if opt.get("--lazy-apps") or opt.get("--lazy"): - # We're not running in preforking mode, nothing to do. - return True - - from warnings import warn + elif not lazy_mode and not (threads_enabled and fork_hooks_on): + from warnings import warn - warn( - Warning( - "We detected the use of uWSGI in preforking mode. " - "This might lead to issues with workers when a background thread " - "(e.g. profiler) is active before the process is forked. " - 'Please run uWSGI with the "--lazy-apps" flag for full support.' + warn( + Warning( + "We detected the use of uWSGI in preforking mode without " + "thread support. This might lead to crashing workers. " + 'Please run uWSGI with the both the "--enable-threads" and ' + '"--py-call-uwsgi-fork-hooks" flags for full support.' + ) ) - ) - return False + return False diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 7e2659810d..18eb2eab14 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -4,7 +4,13 @@ import random import socket -from sentry_sdk._compat import datetime_utcnow, string_types, text_type, iteritems +from sentry_sdk._compat import ( + datetime_utcnow, + string_types, + text_type, + iteritems, + check_uwsgi_thread_support, +) from sentry_sdk.utils import ( capture_internal_exceptions, current_stacktrace, @@ -18,7 +24,7 @@ ) from sentry_sdk.serializer import serialize from sentry_sdk.tracing import trace, has_tracing_enabled -from sentry_sdk.transport import make_transport +from sentry_sdk.transport import HttpTransport, make_transport from sentry_sdk.consts import ( DEFAULT_MAX_VALUE_LENGTH, DEFAULT_OPTIONS, @@ -249,28 +255,15 @@ def _capture_envelope(envelope): self.metrics_aggregator = None # type: Optional[MetricsAggregator] experiments = self.options.get("_experiments", {}) - if experiments.get("enable_metrics", True) or experiments.get( - "force_enable_metrics", False - ): - try: - import uwsgi # type: ignore - except ImportError: - uwsgi = None - - if uwsgi is not None and not experiments.get( - "force_enable_metrics", False - ): - logger.warning("Metrics currently not supported with uWSGI.") - - else: - from sentry_sdk.metrics import MetricsAggregator - - self.metrics_aggregator = MetricsAggregator( - capture_func=_capture_envelope, - enable_code_locations=bool( - experiments.get("metric_code_locations", True) - ), - ) + if experiments.get("enable_metrics", True): + from sentry_sdk.metrics import MetricsAggregator + + self.metrics_aggregator = MetricsAggregator( + capture_func=_capture_envelope, + enable_code_locations=bool( + experiments.get("metric_code_locations", True) + ), + ) max_request_body_size = ("always", "never", "small", "medium") if self.options["max_request_body_size"] not in max_request_body_size: @@ -316,6 +309,16 @@ def _capture_envelope(envelope): self._setup_instrumentation(self.options.get("functions_to_trace", [])) + if ( + self.monitor + or self.metrics_aggregator + or has_profiling_enabled(self.options) + or isinstance(self.transport, HttpTransport) + ): + # If we have anything on that could spawn a background thread, we + # need to check if it's safe to use them. + check_uwsgi_thread_support() + @property def dsn(self): # type: () -> Optional[str] diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 64e2cdf521..ad7b1099ae 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -46,7 +46,6 @@ "transport_zlib_compression_level": Optional[int], "transport_num_pools": Optional[int], "enable_metrics": Optional[bool], - "force_enable_metrics": Optional[bool], "metrics_summary_sample_rate": Optional[float], "should_summarize_metric": Optional[Callable[[str, MetricTags], bool]], "before_emit_metric": Optional[Callable[[str, MetricTags], bool]], diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 323f03b506..21b59283aa 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -2,7 +2,7 @@ import sys from contextlib import contextmanager -from sentry_sdk._compat import check_uwsgi_support, with_metaclass +from sentry_sdk._compat import with_metaclass from sentry_sdk.consts import INSTRUMENTER from sentry_sdk.scope import Scope from sentry_sdk.client import Client @@ -102,7 +102,6 @@ def _init(*args, **kwargs): client = Client(*args, **kwargs) # type: ignore Hub.current.bind_client(client) _check_python_deprecations() - check_uwsgi_support() rv = _InitGuard(client) return rv diff --git a/sentry_sdk/worker.py b/sentry_sdk/worker.py index 02628b9b29..27b2f2f69c 100644 --- a/sentry_sdk/worker.py +++ b/sentry_sdk/worker.py @@ -2,7 +2,6 @@ import threading from time import sleep, time -from sentry_sdk._compat import check_thread_support from sentry_sdk._queue import Queue, FullError from sentry_sdk.utils import logger from sentry_sdk.consts import DEFAULT_QUEUE_SIZE @@ -21,7 +20,6 @@ class BackgroundWorker(object): def __init__(self, queue_size=DEFAULT_QUEUE_SIZE): # type: (int) -> None - check_thread_support() self._queue = Queue(queue_size) # type: Queue self._lock = threading.Lock() self._thread = None # type: Optional[threading.Thread] From ffd215640229b36f6c5ff2c55a792356fc7f59b4 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 19:51:50 +0100 Subject: [PATCH 08/17] fixes --- sentry_sdk/_compat.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index 3d52d610fb..a964137e7a 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -172,7 +172,7 @@ def check_uwsgi_thread_support(): Warning( "We detected the use of uWSGI without thread support. " "This might lead to unexpected issues with the Sentry SDK. " - 'Please run uWSGI with the "--enable-threads" flag for full support.' + 'Please run uWSGI with "--enable-threads" for full support.' ) ) @@ -185,9 +185,11 @@ def check_uwsgi_thread_support(): Warning( "We detected the use of uWSGI in preforking mode without " "thread support. This might lead to crashing workers. " - 'Please run uWSGI with the both the "--enable-threads" and ' - '"--py-call-uwsgi-fork-hooks" flags for full support.' + 'Please run uWSGI with the both "--enable-threads" and ' + '"--py-call-uwsgi-fork-hooks" for full support.' ) ) return False + + return True From 7a663c1409a8ae3190cbbcd2b35af31fa63f545c Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 20:29:41 +0100 Subject: [PATCH 09/17] test --- tests/test_client.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index fa55c1111a..56fd14068f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1316,3 +1316,25 @@ def test_error_sampler(_, sentry_init, capture_events, test_config): # Ensure two arguments (the event and hint) were passed to the sampler function assert len(test_config.sampler_function_mock.call_args[0]) == 2 + + +@pytest.mark.parametrize( + "opt,warning", + [ + [{"enable-threads": True, "lazy-apps": True}, None], + [{"enable-threads": True, "py-call-uwsgi-fork-hooks": True}, None], + [{}, True], + [{"enable-threads": True}, True], + [{"py-call-uwsgi-fork-hooks": True}, True], + [{"lazy-apps": True}, True], + ], +) +def test_uwsgi_warnings(sentry_init, recwarn, opt, warning): + uwsgi = mock.MagicMock() + uwsgi.opt = opt + with mock.patch.dict("sys.modules", uwsgi=uwsgi): + sentry_init(profiles_sample_rate=1.0) + if warning: + assert recwarn + else: + assert not recwarn From 5562b774f23d7e135277122a8cf917d575a45e81 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Wed, 14 Feb 2024 20:36:17 +0100 Subject: [PATCH 10/17] really? --- tests/test_client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_client.py b/tests/test_client.py index 56fd14068f..108ae2637e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1318,6 +1318,7 @@ def test_error_sampler(_, sentry_init, capture_events, test_config): assert len(test_config.sampler_function_mock.call_args[0]) == 2 +@pytest.mark.forked @pytest.mark.parametrize( "opt,warning", [ From feb9d80c531ad4e469e0cc4b8b030305444293b8 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 15 Feb 2024 09:32:50 +0100 Subject: [PATCH 11/17] wording --- sentry_sdk/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index a964137e7a..c04d4ee802 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -171,7 +171,7 @@ def check_uwsgi_thread_support(): warn( Warning( "We detected the use of uWSGI without thread support. " - "This might lead to unexpected issues with the Sentry SDK. " + "This might lead to unexpected issues. " 'Please run uWSGI with "--enable-threads" for full support.' ) ) From c3af993ad73f0f22ef1d20f9ec50a792d560e0c6 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 15 Feb 2024 10:02:52 +0100 Subject: [PATCH 12/17] wording again --- sentry_sdk/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index c04d4ee802..f4719b0b32 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -185,7 +185,7 @@ def check_uwsgi_thread_support(): Warning( "We detected the use of uWSGI in preforking mode without " "thread support. This might lead to crashing workers. " - 'Please run uWSGI with the both "--enable-threads" and ' + 'Please run uWSGI with both "--enable-threads" and ' '"--py-call-uwsgi-fork-hooks" for full support.' ) ) From 33ea68533e5e57d09de0a3264b79c3c5c46e4a33 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 15 Feb 2024 10:06:14 +0100 Subject: [PATCH 13/17] add something in all caps --- sentry_sdk/_compat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index f4719b0b32..bc9642b23a 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -170,6 +170,7 @@ def check_uwsgi_thread_support(): warn( Warning( + "IMPORTANT: " "We detected the use of uWSGI without thread support. " "This might lead to unexpected issues. " 'Please run uWSGI with "--enable-threads" for full support.' @@ -183,6 +184,7 @@ def check_uwsgi_thread_support(): warn( Warning( + "IMPORTANT: " "We detected the use of uWSGI in preforking mode without " "thread support. This might lead to crashing workers. " 'Please run uWSGI with both "--enable-threads" and ' From 828c36c4b461713682fddf01b81407ed47b0272b Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 15 Feb 2024 11:35:49 +0100 Subject: [PATCH 14/17] better test and flag checks --- sentry_sdk/_compat.py | 24 +++++++++++++++++++----- tests/test_client.py | 37 ++++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index bc9642b23a..0885820ddd 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta from functools import wraps +from sentry_sdk.consts import FALSE_VALUES from sentry_sdk._types import TYPE_CHECKING if TYPE_CHECKING: @@ -151,7 +152,7 @@ def check_uwsgi_thread_support(): # the --py-call-uwsgi-fork-hooks option for the SDK to work properly. This # is because any background threads spawned before the main process is # forked are NOT CLEANED UP IN THE CHILDREN BY DEFAULT even if - # --enable-threads is on. One has to also explicitly provide + # --enable-threads is on. One has to explicitly provide # --py-call-uwsgi-fork-hooks to force uWSGI to run regular cpython # after-fork hooks that take care of cleaning up stale thread data. try: @@ -159,11 +160,24 @@ def check_uwsgi_thread_support(): except ImportError: return True + def enabled(option): + value = opt.get(option, False) + if isinstance(value, bool): + return value + + if isinstance(value, bytes): + try: + value = value.decode() + except: # noqa: E722 + pass + + return value and str(value).lower() not in FALSE_VALUES + # When `threads` is passed in as a uwsgi option, # `enable-threads` is implied on. - threads_enabled = bool("threads" in opt or opt.get("enable-threads")) - fork_hooks_on = bool(opt.get("py-call-uwsgi-fork-hooks")) - lazy_mode = bool(opt.get("lazy-apps") or opt.get("lazy")) + threads_enabled = "threads" in opt or enabled("enable-threads") + fork_hooks_on = enabled("py-call-uwsgi-fork-hooks") + lazy_mode = enabled("lazy-apps") or enabled("lazy") if lazy_mode and not threads_enabled: from warnings import warn @@ -179,7 +193,7 @@ def check_uwsgi_thread_support(): return False - elif not lazy_mode and not (threads_enabled and fork_hooks_on): + elif not lazy_mode and (not threads_enabled or not fork_hooks_on): from warnings import warn warn( diff --git a/tests/test_client.py b/tests/test_client.py index 108ae2637e..fc2dd9885b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -5,8 +5,8 @@ import subprocess import sys import time - from textwrap import dedent + from sentry_sdk import ( Hub, Client, @@ -1320,22 +1320,37 @@ def test_error_sampler(_, sentry_init, capture_events, test_config): @pytest.mark.forked @pytest.mark.parametrize( - "opt,warning", + "opt,missing_flags", [ - [{"enable-threads": True, "lazy-apps": True}, None], - [{"enable-threads": True, "py-call-uwsgi-fork-hooks": True}, None], - [{}, True], - [{"enable-threads": True}, True], - [{"py-call-uwsgi-fork-hooks": True}, True], - [{"lazy-apps": True}, True], + # lazy mode with enable-threads, no warning + [{"enable-threads": True, "lazy-apps": True}, []], + [{"enable-threads": "true", "lazy-apps": b"1"}, []], + # preforking mode with enable-threads and py-call-uwsgi-fork-hooks, no warning + [{"enable-threads": True, "py-call-uwsgi-fork-hooks": True}, []], + [{"enable-threads": b"true", "py-call-uwsgi-fork-hooks": b"on"}, []], + # lazy mode, no enable-threads, warning + [{"lazy-apps": True}, ["--enable-threads"]], + [{"enable-threads": b"false", "lazy-apps": True}, ["--enable-threads"]], + # preforking mode, no enable-threads or py-call-uwsgi-fork-hooks, warning + [{}, ["--enable-threads", "--py-call-uwsgi-fork-hooks"]], + [{"processes": b"2"}, ["--enable-threads", "--py-call-uwsgi-fork-hooks"]], + [{"enable-threads": True}, ["--py-call-uwsgi-fork-hooks"]], + [ + {"enable-threads": b"false"}, + ["--enable-threads", "--py-call-uwsgi-fork-hooks"], + ], + [{"py-call-uwsgi-fork-hooks": True}, ["--enable-threads"]], ], ) -def test_uwsgi_warnings(sentry_init, recwarn, opt, warning): +def test_uwsgi_warnings(sentry_init, recwarn, opt, missing_flags): uwsgi = mock.MagicMock() uwsgi.opt = opt with mock.patch.dict("sys.modules", uwsgi=uwsgi): sentry_init(profiles_sample_rate=1.0) - if warning: - assert recwarn + if missing_flags: + assert len(recwarn) == 1 + record = recwarn.pop() + for flag in missing_flags: + assert flag in str(record.message) else: assert not recwarn From c05cc0786270e0ba6ab783855bb090782b43fabc Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 15 Feb 2024 11:40:53 +0100 Subject: [PATCH 15/17] more tests --- tests/test_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index fc2dd9885b..0954a8c5e8 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1331,10 +1331,12 @@ def test_error_sampler(_, sentry_init, capture_events, test_config): # lazy mode, no enable-threads, warning [{"lazy-apps": True}, ["--enable-threads"]], [{"enable-threads": b"false", "lazy-apps": True}, ["--enable-threads"]], + [{"enable-threads": b"0", "lazy": True}, ["--enable-threads"]], # preforking mode, no enable-threads or py-call-uwsgi-fork-hooks, warning [{}, ["--enable-threads", "--py-call-uwsgi-fork-hooks"]], [{"processes": b"2"}, ["--enable-threads", "--py-call-uwsgi-fork-hooks"]], [{"enable-threads": True}, ["--py-call-uwsgi-fork-hooks"]], + [{"enable-threads": b"1"}, ["--py-call-uwsgi-fork-hooks"]], [ {"enable-threads": b"false"}, ["--enable-threads", "--py-call-uwsgi-fork-hooks"], From 3677b943412a7caf5bd9220007a41b03128aceb2 Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 15 Feb 2024 11:42:40 +0100 Subject: [PATCH 16/17] flake8 --- sentry_sdk/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index 0885820ddd..da3e5fc4bd 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -168,7 +168,7 @@ def enabled(option): if isinstance(value, bytes): try: value = value.decode() - except: # noqa: E722 + except Exception: pass return value and str(value).lower() not in FALSE_VALUES From 4f91d53048a14e4506050a5f06569bdb6e34a8ff Mon Sep 17 00:00:00 2001 From: Ivana Kellyerova Date: Thu, 15 Feb 2024 11:45:45 +0100 Subject: [PATCH 17/17] fix --- sentry_sdk/_compat.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index da3e5fc4bd..38872051ff 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -3,7 +3,6 @@ from datetime import datetime, timedelta from functools import wraps -from sentry_sdk.consts import FALSE_VALUES from sentry_sdk._types import TYPE_CHECKING if TYPE_CHECKING: @@ -160,7 +159,10 @@ def check_uwsgi_thread_support(): except ImportError: return True + from sentry_sdk.consts import FALSE_VALUES + def enabled(option): + # type: (str) -> bool value = opt.get(option, False) if isinstance(value, bool): return value