Skip to content

fix: prevent concurrent memory hygiene passes and Windows file lock errors - #535

Merged
ilblackdragon merged 3 commits into
mainfrom
fix/495-hygiene-concurrent-execution
Mar 6, 2026
Merged

ilblackdragon merged 3 commits into
mainfrom
fix/495-hygiene-concurrent-execution

Conversation

@zmanian

@zmanian zmanian commented Mar 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #495.

The heartbeat system spawns run_if_due() via tokio::spawn on every tick (default 30 min), creating a TOCTOU race where multiple tasks read the state file before any saves it, causing all to execute concurrently. On Windows this also triggers OS error 1224 when multiple tasks write the same file simultaneously.

Three fixes:

  • AtomicBool guard (RUNNING + RunningGuard RAII) ensures only one hygiene pass runs at a time
  • State saved before cleanup (not after) to claim the cadence window early and close the TOCTOU race window
  • Atomic file write (write to .json.tmp, then rename) avoids Windows file-locking errors from concurrent writers

Test plan

  • cargo test hygiene -- 8 passed (including 2 new regression tests)
  • cargo clippy --all --all-features -- zero warnings
  • Manual: verify on Windows that the "os error 1224" no longer appears during AFK periods

Generated with Claude Code

…rrors (#495)

The heartbeat system spawns hygiene passes via tokio::spawn on every
tick, creating a TOCTOU race where multiple tasks read the state file
before any saves, causing all to execute concurrently. On Windows this
also triggers OS error 1224 (file locked by memory-mapped section)
when multiple tasks call std::fs::write on the same file.

Three fixes:
- AtomicBool guard (RUNNING + RunningGuard RAII) ensures only one
  hygiene pass runs at a time
- State file is saved before cleanup (not after) to claim the cadence
  window early and close the TOCTOU race
- Atomic file write (write to .tmp then rename) avoids Windows
  file-locking errors from concurrent writers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added scope: workspace Persistent memory / workspace size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Mar 4, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses issues related to concurrent execution of memory hygiene passes, specifically preventing race conditions and resolving Windows file-locking errors. The changes ensure that hygiene operations are serialized and state updates are handled robustly, improving the reliability and stability of the system's cleanup routines.

Highlights

  • Concurrency Control: Implemented an AtomicBool guard (RUNNING with RunningGuard RAII) to ensure that only one memory hygiene pass executes at a time, preventing race conditions.
  • Cadence Window Management: Modified the hygiene process to save the state before performing cleanup. This claims the cadence window earlier, reducing the time frame for Time-of-Check to Time-of-Use (TOCTOU) race conditions.
  • Atomic File Writes: Refactored the state saving mechanism to use an atomic file write pattern (write to a .json.tmp file, then rename). This mitigates Windows file-locking errors (OS error 1224) that can occur with concurrent write attempts.
Changelog
  • src/workspace/hygiene.rs
    • Updated module documentation to reflect the new concurrency guard and revised hygiene pass steps.
    • Added AtomicBool and Ordering imports for concurrency control.
    • Introduced a static AtomicBool named RUNNING to track if a hygiene pass is active.
    • Modified the run_if_due function to acquire the RUNNING guard, skipping execution if another pass is already in progress.
    • Created a RunningGuard RAII struct to automatically release the RUNNING guard when run_if_due exits.
    • Relocated the save_state call within run_if_due to occur before the cleanup logic.
    • Refactored the save_state function to implement an atomic write strategy using a temporary file and std::fs::rename.
    • Enhanced error handling and logging within save_state for directory creation and file operations.
    • Added two new test cases: one to verify the atomic nature of save_state and another to confirm the RUNNING guard prevents re-entry.
Activity
  • The author has provided a detailed summary of the changes and the problem being solved.
  • A test plan is outlined, including unit tests (cargo test hygiene), static analysis (cargo clippy), and a manual verification step for Windows.
  • Unit tests for hygiene passed (8 tests, including 2 new regression tests).
  • Static analysis with cargo clippy yielded zero warnings.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a concurrency issue in the memory hygiene system. The introduction of an AtomicBool with an RAII guard is a robust solution to prevent concurrent hygiene passes, and the switch to an atomic file write pattern correctly resolves potential file locking errors on Windows. The logic is sound, the changes are well-documented, and the new regression tests are a valuable addition that verifies the fixes. I have one minor suggestion to improve the new test's robustness against parallel execution.

Comment thread src/workspace/hygiene.rs

/// Regression test for issue #495: concurrent hygiene passes should be
/// serialized by the AtomicBool guard.
#[test]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test modifies the global static RUNNING. Since cargo test runs tests in parallel by default, this could lead to flaky tests if other tests that also interact with the hygiene system are added in the future. To ensure this test is robust and doesn't interfere with other tests, it's best to serialize access to the shared global state. You can achieve this by adding a static Mutex at the tests module level and acquiring a lock at the beginning of this test.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 6a1a0a4. Added a static RUNNING_TESTS: Mutex<()> to the test module and the running_guard_prevents_reentry test now acquires it before touching the global RUNNING AtomicBool.

zmanian and others added 2 commits March 4, 2026 09:00
Address PR review feedback: the running_guard_prevents_reentry test
manipulates a global static AtomicBool, which could cause flaky
failures if future tests also touch it and run in parallel. A test-only
Mutex ensures serialization.

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ilblackdragon
ilblackdragon merged commit df49b17 into main Mar 6, 2026
14 checks passed
@ilblackdragon
ilblackdragon deleted the fix/495-hygiene-concurrent-execution branch March 6, 2026 02:23
This was referenced Mar 6, 2026
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…rrors (nearai#535)

* fix: prevent concurrent memory hygiene passes and Windows file lock errors (nearai#495)

The heartbeat system spawns hygiene passes via tokio::spawn on every
tick, creating a TOCTOU race where multiple tasks read the state file
before any saves, causing all to execute concurrently. On Windows this
also triggers OS error 1224 (file locked by memory-mapped section)
when multiple tasks call std::fs::write on the same file.

Three fixes:
- AtomicBool guard (RUNNING + RunningGuard RAII) ensures only one
  hygiene pass runs at a time
- State file is saved before cleanup (not after) to claim the cadence
  window early and close the TOCTOU race
- Atomic file write (write to .tmp then rename) avoids Windows
  file-locking errors from concurrent writers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add Mutex to serialize tests touching global RUNNING AtomicBool

Address PR review feedback: the running_guard_prevents_reentry test
manipulates a global static AtomicBool, which could cause flaky
failures if future tests also touch it and run in parallel. A test-only
Mutex ensures serialization.

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: workspace Persistent memory / workspace size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

memory hygiene error

2 participants