Skip to content

fix!: do not count downtime towards crawler_runtime after migration or resurrection - #2204

Draft
Pijukatel wants to merge 4 commits into
masterfrom
claude/actor-resurrection-time-tracking-bixxkl
Draft

fix!: do not count downtime towards crawler_runtime after migration or resurrection#2204
Pijukatel wants to merge 4 commits into
masterfrom
claude/actor-resurrection-time-tracking-bixxkl

Conversation

@Pijukatel

@Pijukatel Pijukatel commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes crawler_runtime miscounting when a run is resumed from a persisted state (migration, resurrection, abort), targeting the v2 release. End-user behavior is unchanged except for the runtime-counting fixes.

Guarantees after this change:

  • A fresh run's first statistics log reports a runtime of exactly zero.
  • A resumed run's first statistics log reports exactly the runtime of the previously persisted state, not the downtime between the runs (previously it included the whole downtime, e.g. 38s, and "corrected" itself later).
  • The runtime accumulates across any number of persist/resume cycles: it is restored from the persisted crawlerRuntimeMillis value, which every persist writes as the live total, so nothing is lost with repeated resurrections (previously only the last run's segment survived).
  • Aborts are handled: a state persisted mid-run carries the total up to that moment, and a resumed run continues from the last persisted total, losing only the tail since the last persist. For states persisted by older versions (no usable crawlerRuntimeMillis), the end of an uncleanly finished run is approximated by stats_persisted_at, so downtime is not baked into the total.
  • Robust loading: an invalid persisted crawlerRuntimeMillis (null, garbage) does not fail loading the state - it falls back to the timestamp reconstruction; a negative value is clamped to zero.

Breaking changes (v2)

  • StatisticsState.crawler_runtime is a read-only property; the deprecated setter is removed (closes Remove deprecated StatisticsState.crawler_runtime setter #1567).
  • StatisticsState.crawler_runtime_for_serialization is removed; the runtime_offset field serializes the live runtime under crawlerRuntimeMillis and restores it on load.
  • crawlerRuntimeMillis is serialized as a number of milliseconds (matching the other *Millis fields and Crawlee for JavaScript) instead of an ISO 8601 duration string. States persisted by v1.x load correctly.
  • The never-populated state fields errors, retry_errors, requests_finished_per_minute and requests_failed_per_minute are removed from StatisticsState. The FinalStatistics fields of the same names and the logged per-minute rates are unaffected.

The changes are documented in the new docs/upgrading/upgrading_to_v2.md.

Issues

Testing

  • Regression tests in tests/unit/_statistics/test_persistence.py cover: fresh run's first log is exactly zero; resumed run's first log equals the persisted runtime without a simulated 2-hour downtime; the total accumulates across multiple resurrections; unclean shutdowns do not bake downtime into the total; v1.x ISO-string states load; null/garbage crawlerRuntimeMillis falls back to timestamps; negative values are clamped.
  • poe lint, poe type-check, and the unit test suite pass locally on Python 3.10 and 3.13 (only the pre-existing browser-environment failures unrelated to this change).

Checklist

  • CI passed

🤖 Generated with Claude Code

https://claude.ai/code/session_01VMHwDx87jV4rnu5LkbpNxy

… resurrection

When a crawler run was resumed from a persisted state, two issues inflated
the reported `crawler_runtime`:

- `Statistics.__aenter__` started the periodic logger before updating
  `crawler_last_started_at`, so the first statistics log of the resumed run
  computed the runtime from the previous run's start time and included all
  the downtime between the runs (later stats were computed correctly, which
  made the value appear to "jump back").
- When the state came from a run that did not finish cleanly (migration,
  abort), `StatisticsState.model_post_init` computed the accumulated runtime
  offset using the current time, permanently baking the downtime between the
  runs into all subsequent statistics. The offset is now computed from the
  moment the state was last persisted instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VMHwDx87jV4rnu5LkbpNxy
@github-actions github-actions Bot added this to the 148th sprint - Tooling team milestone Aug 31, 2026
@github-actions github-actions Bot added t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics. labels Aug 31, 2026
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.75%. Comparing base (142ec58) to head (4b9f3e5).
⚠️ Report is 8 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2204      +/-   ##
==========================================
+ Coverage   93.74%   93.75%   +0.01%     
==========================================
  Files         181      181              
  Lines       12852    12862      +10     
==========================================
+ Hits        12048    12059      +11     
+ Misses        804      803       -1     
Flag Coverage Δ
unit 93.75% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The first periodic statistics log must report exactly the runtime
accumulated by previous runs: zero for a fresh start, the previous total
for a resumed run. `Statistics.__aenter__` now resets the run timestamps
before starting the periodic logger and sets the new start time after the
initial log entry is printed.

The accumulated runtime is now also restored from the persisted
`crawlerRuntimeMillis` value instead of being reconstructed from the last
run's timestamps, so runs before the most recent one are no longer lost
from the total, and a state persisted mid-run keeps the correct runtime.
The timestamp-based reconstruction is kept as a fallback for states
persisted by older versions. `crawlerRuntimeMillis` is also serialized as
milliseconds now, matching its name and the other `*Millis` fields,
instead of an ISO 8601 duration string.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VMHwDx87jV4rnu5LkbpNxy
… on Python 3.10

`datetime.fromisoformat` does not accept the 'Z' suffix until Python 3.11,
which failed the resumed-run test on the 3.10 CI jobs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VMHwDx87jV4rnu5LkbpNxy
@Pijukatel

Copy link
Copy Markdown
Collaborator Author

Waiting for V2 as this might be breaking change and will inlcude removal of deprecated fields

Remove statistics API that is deprecated or was never populated, and make
loading of persisted states tolerant of invalid runtime values:

- Remove the deprecated `StatisticsState.crawler_runtime` setter;
  the property is read-only now.
- Remove `StatisticsState.crawler_runtime_for_serialization`; the
  `runtime_offset` field serializes the live runtime under
  `crawlerRuntimeMillis` itself and restores it when a state is loaded.
- Remove the never-populated state fields `errors`, `retry_errors`,
  `requests_finished_per_minute` and `requests_failed_per_minute`.
  The `FinalStatistics` fields of the same names are unaffected.
- An invalid persisted `crawlerRuntimeMillis` value no longer fails
  loading the whole state: it is treated as absent and the runtime is
  reconstructed from the timestamps, as for states persisted by older
  versions. A negative persisted value is clamped to zero.

Add the v2 upgrading guide describing the statistics changes.

BREAKING CHANGE: `StatisticsState.crawler_runtime` is read-only,
`StatisticsState.crawler_runtime_for_serialization` and the unused state
fields `errors`, `retry_errors`, `requests_finished_per_minute` and
`requests_failed_per_minute` were removed, and `crawlerRuntimeMillis` is
serialized as a number of milliseconds instead of an ISO 8601 duration
string. Closes #1567.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VMHwDx87jV4rnu5LkbpNxy
@Pijukatel Pijukatel changed the title fix: do not count downtime towards crawler_runtime after migration or resurrection fix!: do not count downtime towards crawler_runtime after migration or resurrection Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove deprecated StatisticsState.crawler_runtime setter

3 participants