From 360960461e5141a49cc8b4c8fdc7ab8265c6547d Mon Sep 17 00:00:00 2001 From: David Berenstein Date: Wed, 12 Aug 2026 23:30:54 +0200 Subject: [PATCH] fix: resume the scheduler in stop_task only if start_task paused it start_task stops the periodic scheduler but nothing ever restarted it, so a tracker started with start() lost its periodic measurements after the first task. Restarting it unconditionally would instead leave a 1s scheduler running for pure start_task/stop_task users, so track whether start_task actually paused a running scheduler. Co-Authored-By: Claude Opus 5 (1M context) --- codecarbon/emissions_tracker.py | 9 +++++++++ tests/test_emissions_tracker.py | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/codecarbon/emissions_tracker.py b/codecarbon/emissions_tracker.py index 96ed00c91..ead914a24 100644 --- a/codecarbon/emissions_tracker.py +++ b/codecarbon/emissions_tracker.py @@ -296,6 +296,7 @@ def _initialize_runtime_state(self) -> None: self._tasks: Dict[str, Task] = {} self._active_task: Optional[str] = None self._active_task_emissions_at_start: Optional[EmissionsData] = None + self._scheduler_paused_by_task = False self._hardware = [] self._hardware_initialized = False @@ -752,6 +753,10 @@ def start_task(self, task_name=None) -> None: # Stop scheduler as we do not want it to interfere with the task measurement if self._scheduler: + # Only resume it in stop_task if it was actually running, i.e. the tracker + # was started with start(). Pure start_task/stop_task usage must not leave + # a periodic measurement running behind. + self._scheduler_paused_by_task = not self._scheduler._stopped self._scheduler.stop() # Task background thread for measuring power @@ -848,6 +853,10 @@ def stop_task(self, task_name: str = None) -> EmissionsData: self._active_task = None self._active_task_emissions_at_start = None # Clear task-specific start data + if self._scheduler is not None and self._scheduler_paused_by_task: + self._scheduler_paused_by_task = False + self._scheduler.start() + return task_emission_data @suppress(Exception) diff --git a/tests/test_emissions_tracker.py b/tests/test_emissions_tracker.py index 8ab12e5d8..f5699b510 100644 --- a/tests/test_emissions_tracker.py +++ b/tests/test_emissions_tracker.py @@ -765,6 +765,32 @@ def test_start_task_returns_when_engine_initialization_fails( any("Tracker not initialized" in message for message in logs.output) ) + def test_stop_task_resumes_scheduler_only_if_start_task_paused_it( + self, + mock_cli_setup, + mock_log_values, + mocked_get_gpu_details, + mocked_env_cloud_details, + mocked_get_gpu_utilization_list, + mocked_is_gpu_details_available, + mocked_is_nvidia_system, + ): + # Pure start_task/stop_task usage: no periodic scheduler must be left running. + tracker = EmissionsTracker(save_to_file=False) + tracker.start_task("task-only") + tracker.stop_task() + self.assertTrue(tracker._scheduler._stopped) + tracker.stop() + + # start() then start_task/stop_task: the paused scheduler must be resumed. + tracker = EmissionsTracker(save_to_file=False) + tracker.start() + tracker.start_task("task-in-run") + self.assertTrue(tracker._scheduler._stopped) + tracker.stop_task() + self.assertFalse(tracker._scheduler._stopped) + tracker.stop() + @mock.patch("codecarbon.external.ram.RAM.measure_power_and_energy") @mock.patch("codecarbon.external.hardware.CPU.measure_power_and_energy") @mock.patch(