mlco2 / mlco2/codecarbon

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

Open
#1,328 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.9k
Forks
323
Avg merge
1d 12h
Merged PRs (30d)
12

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in codecarbon/emissions_tracker.py, especially start() at lines 708-725, stop() at lines 909-914, and _initialize_scheduler_state() at line 344. Trace how _start_time and both scheduler fields change across a start-stop-start cycle. Done means a second start resumes periodic measurement and the next row reports active tracking duration without the idle gap.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.