mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-30 23:02:50 +00:00
fix(telemetry): skip signal handler registration in non-main threads
* fix(telemetry): skip signal handler registration in non-main threads When CrewAI is initialized from a non-main thread (e.g. Streamlit, Flask, Django, Jupyter), the telemetry module attempted to register signal handlers which only work in the main thread. This caused multiple noisy ValueError tracebacks to be printed to stderr, confusing users even though the errors were caught and non-fatal. Check `threading.current_thread() is not threading.main_thread()` before attempting signal registration, and skip silently with a debug-level log message instead of printing full tracebacks. Fixes crewAIInc/crewAI#4289 * fix(test): move Telemetry() inside signal.signal mock context Refs: #4649 * fix(telemetry): move signal.signal mock inside thread to wrap Telemetry() construction The patch context now activates inside init_in_thread so the mock is guaranteed to be active before and during Telemetry.__init__, addressing the Copilot review feedback. Refs: #4289 * fix(test): mock logger.debug instead of capsys for deterministic assertion Replace signal.signal-only mock with combined logger + signal mock. Assert logger.debug was called with the skip message and signal.signal was never invoked from the non-main thread. Refs: #4289
This commit is contained in:
@@ -173,6 +173,12 @@ class Telemetry:
|
||||
|
||||
self._original_handlers: dict[int, Any] = {}
|
||||
|
||||
if threading.current_thread() is not threading.main_thread():
|
||||
logger.debug(
|
||||
"Skipping signal handler registration: not running in main thread"
|
||||
)
|
||||
return
|
||||
|
||||
self._register_signal_handler(signal.SIGTERM, SigTermEvent, shutdown=True)
|
||||
self._register_signal_handler(signal.SIGINT, SigIntEvent, shutdown=True)
|
||||
if hasattr(signal, "SIGHUP"):
|
||||
|
||||
@@ -121,3 +121,41 @@ def test_telemetry_singleton_pattern():
|
||||
thread.join()
|
||||
|
||||
assert all(instance is telemetry1 for instance in instances)
|
||||
|
||||
|
||||
def test_no_signal_handler_traceback_in_non_main_thread():
|
||||
"""Signal handler registration should be silently skipped in non-main threads.
|
||||
|
||||
Regression test for https://github.com/crewAIInc/crewAI/issues/4289
|
||||
"""
|
||||
errors: list[Exception] = []
|
||||
mock_holder: dict = {}
|
||||
|
||||
def init_in_thread():
|
||||
try:
|
||||
Telemetry._instance = None
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"CREWAI_DISABLE_TELEMETRY": "false", "OTEL_SDK_DISABLED": "false"},
|
||||
),
|
||||
patch("crewai.telemetry.telemetry.TracerProvider"),
|
||||
patch("signal.signal") as mock_signal,
|
||||
patch("crewai.telemetry.telemetry.logger") as mock_logger,
|
||||
):
|
||||
Telemetry()
|
||||
mock_holder["signal"] = mock_signal
|
||||
mock_holder["logger"] = mock_logger
|
||||
except Exception as exc:
|
||||
errors.append(exc)
|
||||
|
||||
thread = threading.Thread(target=init_in_thread)
|
||||
thread.start()
|
||||
thread.join()
|
||||
|
||||
assert not errors, f"Unexpected error: {errors}"
|
||||
assert mock_holder, "Thread did not execute"
|
||||
mock_holder["signal"].assert_not_called()
|
||||
mock_holder["logger"].debug.assert_any_call(
|
||||
"Skipping signal handler registration: not running in main thread"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user