Skip to content

fix(cards): use time.monotonic() for async-process timeout#3225

Merged
talsperre merged 3 commits into
Netflix:masterfrom
alexzhu0:fix/card-creator-monotonic-time
Jun 1, 2026
Merged

fix(cards): use time.monotonic() for async-process timeout#3225
talsperre merged 3 commits into
Netflix:masterfrom
alexzhu0:fix/card-creator-monotonic-time

Conversation

@alexzhu0

Copy link
Copy Markdown
Contributor

Fixes #3224

What

metaflow/plugins/cards/card_creator.py measured async card-rendering subprocess lifetime with time.time(). Replaced three call sites with time.monotonic() and added a regression unit test.

Why

time.time() returns the wall-clock and can step backward under NTP correction, container clock-skew rebase, or manual settimeofday. Two failure modes both bite long-running Card jobs:

  1. Backward jumptime.time() - _async_started becomes small or negative → timeout check never fires → the card subprocess leaks indefinitely in CardProcessManager.async_card_processes.
  2. Forward jump → elapsed delta inflates → subprocess killed prematurely → user sees a missing/incomplete card.

time.monotonic() is the documented stdlib API for interval measurement; it cannot move backward and is unaffected by system-clock adjustments.

How

Three-line change in metaflow/plugins/cards/card_creator.py:

  • L31 — CardProcessManager._register_card_process() now stores time.monotonic() instead of time.time().
  • L200 — _wait_for_async_processes_to_finish() compares with time.monotonic().
  • L231 — _run_command() async branch compares with time.monotonic().

The stored value is opaque to all readers, so swapping the source is safe — there is no API change, no on-disk format change, and no cross-process value that needs to remain compatible.

Tests

New file test/unit/test_card_creator_monotonic.py with three tests:

  1. test_register_uses_monotonic_not_wall_clock — registry stores the monotonic timestamp, not time.time().
  2. test_timeout_check_immune_to_wall_clock_backward_jump — simulates a 1-day wall-clock backward jump while monotonic advances 600 s; verifies the subprocess is still reaped at the 60 s timeout.
  3. test_timeout_check_not_triggered_when_within_window — sanity check that processes still inside the timeout window are not killed.

Local run:

$ pytest test/unit/test_card_creator_monotonic.py -v
test/unit/test_card_creator_monotonic.py::test_register_uses_monotonic_not_wall_clock PASSED
test/unit/test_card_creator_monotonic.py::test_timeout_check_immune_to_wall_clock_backward_jump PASSED
test/unit/test_card_creator_monotonic.py::test_timeout_check_not_triggered_when_within_window PASSED
============================== 3 passed in 0.92s ===============================

Checklist

  • This PR addresses a single concern (one bug fix)
  • The diff is reasonably sized and easy to review (3 lines fix + ~110 lines tests)
  • New functionality is covered by tests (regression test for the backward-jump failure mode)
  • Linting/type checking pass — will rely on CI to confirm; pre-commit not run locally
  • No unrelated changes or drive-by fixes are included

AI tool disclosure

Bug surfaced by an audit grep for time.time() used in interval contexts (an R5 rule in my local PR-audit toolkit). I authored the fix and tests, verified the call sites read as written, ran the tests locally, and can defend the monotonic-vs-wall-clock semantics without reference to an AI tool.

)

`CardProcessManager` measured async card-rendering subprocess lifetime
with `time.time()`, a wall-clock that can step backward under NTP
correction, container clock skew, or manual `settimeofday`. Two failure
modes both bite long-running Card jobs on Kubernetes / Batch:

1. Backward jump → elapsed delta becomes small/negative, timeout
   never fires, card subprocess leaks indefinitely in
   `CardProcessManager.async_card_processes`.
2. Forward jump → elapsed delta inflates, subprocess killed early
   and user sees a missing/incomplete card.

Replace the three call sites in `card_creator.py` (one writer, two
readers) with `time.monotonic()`, the documented stdlib API for
interval measurement.

Adds `test/unit/test_card_creator_monotonic.py` with three tests:
- registry stores monotonic timestamp, not wall-clock
- backward wall-clock jump still triggers timeout-driven reap
- in-window monotonic elapsed leaves a running process alone

Fixes Netflix#3224
@greptile-apps

greptile-apps Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a latent timer bug in CardProcessManager where time.time() was used to measure subprocess elapsed time, making the timeout logic vulnerable to NTP corrections or container clock adjustments. The fix replaces all three call sites with time.monotonic(), which is the correct stdlib API for interval measurement.

  • card_creator.py: Three-line change — _register_card_process now stores a monotonic timestamp, and both timeout-check sites in _wait_for_async_processes_to_finish and _run_command compare against time.monotonic(). The inline schema comment is also updated to reflect the new source.
  • test/unit/test_card_creator.py: Four regression tests covering timestamp storage, backward wall-clock jump immunity, within-window process preservation, and the timed-out process replacement path in _run_command(wait=False).

Confidence Score: 5/5

Safe to merge — the change is minimal, mechanically correct, and all three call sites are consistently updated.

The fix is a straightforward swap of time.time() for time.monotonic() at every read and write site for the elapsed-time value. The stored timestamp is opaque and never crosses a process or serialization boundary, so the change is fully self-contained. The four new regression tests validate the registration path, the timeout-trigger path under a backward clock jump, the within-window no-kill path, and the _run_command replacement path — giving good coverage of the changed logic.

No files require special attention.

Important Files Changed

Filename Overview
metaflow/plugins/cards/card_creator.py Replaces three time.time() call sites with time.monotonic() for subprocess elapsed-time tracking; schema comment on line 23 is also updated to match.
test/unit/test_card_creator.py New regression test file with four tests covering monotonic timestamp registration, backward wall-clock jump immunity, within-window no-kill safety, and timed-out process replacement via _run_command.

Reviews (3): Last reviewed commit: "test(cards): clean up card creator monot..." | Re-trigger Greptile

The class-level schema comment on CardProcessManager.async_card_processes
still documented time.time() after the implementation moved to
time.monotonic(). A future reader relying on the comment could
incorrectly revert the fix. Bring the comment in line with the
implementation.

No behavior change.
@alexzhu0

Copy link
Copy Markdown
Contributor Author

Addressed the P2 finding from Greptile: updated the schema comment on CardProcessManager.async_card_processes (L20-25) to reference time.monotonic() so it matches the implementation. No behavior change.

Pushed as fd46593.

@codecov

codecov Bot commented May 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (master@90ea1d3). Learn more about missing BASE report.

Files with missing lines Patch % Lines
metaflow/plugins/cards/card_creator.py 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master    #3225   +/-   ##
=========================================
  Coverage          ?   28.19%           
=========================================
  Files             ?      381           
  Lines             ?    52373           
  Branches          ?     9247           
=========================================
  Hits              ?    14767           
  Misses            ?    36657           
  Partials          ?      949           

☔ View full report in Codecov by Sentry.
📢 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.

@talsperre
talsperre merged commit 91923d2 into Netflix:master Jun 1, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

card_creator: use time.monotonic() for async-process timeout instead of wall-clock time.time()

2 participants