Skip to content

Fix busy-spin loops causing test_io_enable_disable flakiness - #642

Open
nllong wants to merge 2 commits into
developfrom
fix-io-enable-disable-flakiness
Open

Fix busy-spin loops causing test_io_enable_disable flakiness#642
nllong wants to merge 2 commits into
developfrom
fix-io-enable-disable-flakiness

Conversation

@nllong

@nllong nllong commented Aug 11, 2026

Copy link
Copy Markdown
Member

Summary

Split out from #633, where the underlying flaky-test investigation happened.

tests/integration/test_small_office_osw.py::test_io_enable_disable has been intermittently failing in CI with a wrong output value (e.g. asserting Python Output == 20 but getting 21.735...), unrelated to any change in that PR. Root-caused this to two busy-spin (tight, unslept while loops) bugs in the worker's job/simulation synchronization code:

  • StepRunProcess._wait_for_event (used by advance()) and StepRunProcess.stop() wait on multiprocessing.Manager Event proxies for the event to be cleared, but the loop had no sleep in that branch — it busy-spins at ~100% CPU for the entire duration of every advance()/stop() call.
  • Job._check_messages called redis_pubsub.get_message() with the default non-blocking timeout=0, so start_message_loop spins as fast as possible while idle, hset()-ing the job status to redis on every iteration.

Under CPU contention (constrained CI runners, --scale worker=2 running two of these loops concurrently) this starves the EnergyPlus simulation subprocess of CPU time, making it more likely to overrun advance_timeout and abort/corrupt the run mid-step — producing stale or incorrect point values on the next read. This is the most likely explanation for the intermittent failure.

Changes

  • alfalfa_worker/jobs/step_run_process.py: add a short sleep(0.05) in both busy-spin loops.
  • alfalfa_worker/lib/job.py: use a 1s blocking get_message(timeout=1.0) read instead of a non-blocking poll.
  • tests/worker/lib/mock_redis_pub_sub.py: update the test double to accept the new timeout argument (mirrors redis-py's PubSub.get_message semantics).

Testing

  • Full unit test suite passes locally (poetry run pytest): 30 passed.
  • Ran test_io_enable_disable repeatedly, including with worker containers CPU-limited via docker update --cpus=1.0 (the scenario most likely to trigger the original race) — passed consistently after the fix.
  • pre-commit run passes on all changed files.

Note: I was not able to 100% deterministically reproduce the original race locally even before this fix (it's rare), so this can't be proven to fully eliminate the flakiness — but the busy-spin loops are a real, independently-justified defect (CPU/redis thrashing) and a strong contributing factor given the failure's timing-sensitive nature.

nllong and others added 2 commits August 11, 2026 09:30
- step_run_process.py: advance()/stop() waited on multiprocessing Event
  proxies with a tight, unslept while-loop, pegging a full CPU core for
  the entire duration of every advance() call. Under CPU contention
  (e.g. constrained CI runners, --scale worker=2) this starves the
  simulation subprocess and makes it more likely to overrun
  advance_timeout, corrupting/aborting the run mid-step and producing
  stale/incorrect point values on the next read - the likely cause of
  the intermittent test_io_enable_disable failures. Added a short sleep
  in both loops.
- job.py: start_message_loop's _check_messages() called
  redis_pubsub.get_message() non-blocking in a tight loop, hset()'ing
  the job status to redis on every spin iteration. Switched to a 1s
  blocking read so idle jobs stop hammering redis/CPU while waiting on
  the next message.
- Updated the corresponding test double (MockRedisPubSub) to accept the
  new timeout argument.

Verified locally: full unit test suite passes, and
test_small_office_osw.py::test_io_enable_disable passes repeatedly
(including under artificially CPU-limited worker containers, the
scenario most likely to trigger the original race).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant