diff --git a/gateway/run.py b/gateway/run.py index a0002608b0840..105f654057b5a 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -27830,6 +27830,19 @@ def _start_cron_ticker(stop_event: threading.Event, adapters=None, loop=None, in InProcessCronScheduler().start(stop_event, adapters=adapters, loop=loop, interval=interval) +def _stop_cron_provider(provider) -> None: + """Stop a cron provider without letting it choose the gateway exit code.""" + try: + provider.stop() + except SystemExit as exc: + logger.warning( + "Cron provider stop() attempted to exit the gateway with code %s; ignoring", + exc.code, + ) + except Exception as exc: + logger.debug("Cron provider stop() error: %s", exc) + + # Upper bound for cooperatively draining the cron ticker on shutdown. The cron # thread delivers via ``safe_schedule_threadsafe`` and blocks on # ``future.result(timeout=60)`` (see cron/scheduler.py::_deliver_result), so a @@ -28493,10 +28506,7 @@ async def start_gateway(config: Optional[GatewayConfig] = None, replace: bool = # silently dropped (#58818). Awaiting keeps the loop alive so the in-flight # delivery finishes before we tear down. cron_stop.set() - try: - cron_provider.stop() - except Exception as e: - logger.debug("Cron provider stop() error: %s", e) + _stop_cron_provider(cron_provider) if not await _await_thread_exit(cron_thread, timeout=_CRON_SHUTDOWN_DRAIN_TIMEOUT): logger.warning( "Cron ticker did not exit within %.0fs of shutdown — an in-flight " diff --git a/tests/gateway/test_gateway_shutdown.py b/tests/gateway/test_gateway_shutdown.py index 47ca88af2d2ab..21debe894c7f0 100644 --- a/tests/gateway/test_gateway_shutdown.py +++ b/tests/gateway/test_gateway_shutdown.py @@ -49,6 +49,16 @@ def test_cleanup_agent_resources_reaps_stale_aux_clients(): cleanup_mock.assert_called_once() +def test_cron_provider_stop_cannot_override_gateway_exit_code(caplog): + provider = MagicMock() + provider.stop.side_effect = SystemExit(GATEWAY_SERVICE_RESTART_EXIT_CODE) + + gateway_run._stop_cron_provider(provider) + + provider.stop.assert_called_once_with() + assert "attempted to exit the gateway with code 75; ignoring" in caplog.text + + @pytest.mark.asyncio async def test_gateway_stop_interrupts_running_agents_and_cancels_adapter_tasks(): runner, adapter = make_restart_runner()