Skip to content

Tracker cannot be restarted: start() after stop() is a no-op and the next row's duration is wrong #1328

Open

Description

@davidberenstein1957

Problem

A tracker cannot be restarted. After stop(), a subsequent start() is a no-op ("Already started tracking"), and the next stop() still writes a row — but that row's duration covers everything since the original start(), including the idle gap, and the phase was never sampled. This is the natural pattern for benchmarking loops ("measure phase A, stop, measure phase B") and for notebooks where a cell is re-run.

Reproduction

tracker = OfflineEmissionsTracker(country_iso_code="FRA", measure_power_secs=1)
tracker.start(); time.sleep(3); tracker.stop()   # row 1: duration ~3
time.sleep(3)
tracker.start()                                  # logs "Already started tracking", does nothing
time.sleep(3); tracker.stop()                    # row 2: duration ~9, unsampled
assert tracker._scheduler is None                # no periodic measurement at all in phase B

Root cause

stop() tears down both schedulers but never clears the start time (codecarbon/emissions_tracker.py:909-914):

if self._scheduler:
    self._scheduler.stop()
    self._scheduler = None
if self._scheduler_monitor_power:
    self._scheduler_monitor_power.stop()
    self._scheduler_monitor_power = None

start() then bails out because _start_time is still set (emissions_tracker.py:708-710), and even if that guard is bypassed, self._scheduler.start() at emissions_tracker.py:725 raises AttributeError on None, which the @suppress(Exception) decorator on start() turns into a warning. _initialize_scheduler_state() (emissions_tracker.py:344) is only ever called from __init__ (emissions_tracker.py:604), so the schedulers are never rebuilt.

Lifecycle state is split across _start_time, _scheduler and _scheduler_monitor_power with no single reset path.

Expected vs actual

Expected: start() after stop() resumes tracking; the periodic measurement runs again and the next row's duration reflects active tracking time.

Actual: the second start() is silently a no-op, phase B gets a single catch-all measurement from _measure_power_and_energy_if_stale() at stop(), and the row's duration includes the paused interval — so emissions_rate for that row is wrong too.

Measured on current master (measure_power_secs=1, 3 s per phase): row 1 duration=3.088958, row 2 duration=6.104781.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions