Skip to content

Make mlc pull repo thread safe#265

Open
arjunsuresh with Copilot wants to merge 10 commits into
devfrom
copilot/fix-mlc-pull-repo-thread-safety
Open

Make mlc pull repo thread safe#265
arjunsuresh with Copilot wants to merge 10 commits into
devfrom
copilot/fix-mlc-pull-repo-thread-safety

Conversation

Copilot AI commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Concurrent mlc pull repo calls race on repos.json — two threads can both read, both modify their local copy, and one write overwrites the other's changes. Similarly, two threads can both see a repo directory is absent and race to git clone the same path.

Changes

mlc/repo_action.py

  • Import: Added from filelock import FileLock, Timeout (already a transitive dep via index.py)
  • register_repo: Wraps repos.json read-modify-write in FileLock(repos.json.lock, timeout=60), eliminating lost-update races on concurrent registration
  • unregister_repo: Same FileLock protection for the remove-and-write path
  • pull_repo: Wraps the entire clone/pull/register section in a per-repo FileLock(repo_path + ".lock", timeout=300), preventing double-clone races when two callers simultaneously find the repo absent

tests/test_pull_repo_thread_safety.py

  • test_concurrent_register_repo_no_data_loss: 10 threads concurrently register unique paths → all 10 present in repos.json, no duplicates
  • test_concurrent_unregister_repo_no_data_loss: 10 threads concurrently unregister unique paths → all 10 removed, no duplicates

✅ PR Checklist

  • Target branch is dev

📌 Note: PRs must be raised against dev. Do not commit directly to main.

✅ Testing & CI

  • Have tested the changes in my local environment, else have properly conveyed in the PR description
  • The change includes a GitHub Action to test the script(if it is possible to be added).
  • No existing GitHub Actions are failing because of this change.

📚 Documentation

  • README or help docs are updated for new features or changes.
  • CLI help messages are meaningful and complete.

📁 File Hygiene & Output Handling

  • No unintended files (e.g., logs, cache, temp files, pycache, output folders) are committed.

🛡️ Safety & Security

  • No secrets or credentials are committed.
  • Paths, shell commands, and environment handling are safe and portable.

🙌 Contribution Hygiene

  • PR title and description are concise and clearly state the purpose of the change.
  • All reviewer feedback has been addressed.

Copilot AI linked an issue Jul 18, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Make mlc pull repo thread safe Make mlc pull repo thread safe Jul 18, 2026
Copilot AI requested a review from arjunsuresh July 18, 2026 20:28
@github-actions

Copy link
Copy Markdown

🤖 AI PR Review Summary\n\nThis PR introduces file locking around critical file and directory operations to prevent concurrent access issues. It adds FileLock usage for repos.json read/write and for cloning or pulling git repositories. The design improves concurrency safety but adds complexity and potential deadlock risks if locks are not managed carefully. The stash/apply logic for force pulling with local changes is enhanced with better error handling and rollback. Overall, the changes improve robustness but require careful testing to avoid lock contention or git state inconsistencies.

@github-actions

Copy link
Copy Markdown

MLCommons CLA bot All contributors have signed the MLCommons CLA ✍️ ✅

@github-actions

Copy link
Copy Markdown

🤖 AI PR Review Summary\n\nThis PR introduces file locking around critical file operations to prevent concurrent access issues and adds locking around git repository cloning and pulling to avoid race conditions. It also refactors the force pull logic to stash local changes before pulling and attempts to reapply them after, with improved error handling and rollback on stash apply conflicts. The main risks involve potential deadlocks or lock contention due to long lock hold times during git operations, and complexity in stash conflict recovery. The design improves concurrency safety but increases complexity in error handling and recovery.

@arjunsuresh
arjunsuresh requested a review from anandhu-eng July 18, 2026 21:38

@anandhu-eng anandhu-eng 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.

🤖 Claude Code Review (requested by @anandhusooraj)

Reviewed by Claude (claude-sonnet-4-6) at the request of a maintainer.


Overview

The race conditions described are real: concurrent pull_repo callers can both find the repo directory absent and race to git clone the same path; concurrent register_repo / unregister_repo callers can corrupt repos.json via a lost-update. The FileLock approach using an existing transitive dependency is the right tool. Lock ordering is consistent (repo.lock always acquired before repos.json.lock), so no deadlock risk.


Issues

🔴 The register_repo thread-safety test does not test RepoAction.register_repo

test_concurrent_register_repo_no_data_loss calls self._register_path(path) — a private helper defined in the test class that re-implements the FileLock pattern itself. It never calls RepoAction.register_repo at all.

def _register_path(self, path):
    """Thread-safe repos.json registration (mirrors register_repo logic)."""
    repos_lock_file = self.repos_file + ".lock"
    with FileLock(repos_lock_file, timeout=60):
        ...

If someone removes FileLock from RepoAction.register_repo, this test will still pass. The test validates that FileLock works in general, not that register_repo uses it. The unregister test is correct — it calls the real unregister_repo function.

Fix: Call RepoAction.register_repo (or use RepoAction.pull_repo end-to-end with mocked git) in the register test, the same way the unregister test calls the real function.

🔴 No test for the double-clone race condition

The PR description identifies the double-clone race as the primary problem (two threads can both see a repo directory is absent and race to git clone). There is no test that exercises this path. The two tests cover register/unregister only, not the pull_repo clone guard.

A minimal test: spawn N threads all calling pull_repo for the same (absent) repo URL with git mocked; assert git clone is called exactly once.

🟡 Timeout is imported but never caught explicitly

from filelock import FileLock, Timeout

When FileLock exhausts its timeout it raises filelock.Timeout. Neither pull_repo nor register_repo / unregister_repo catches it specifically — it falls through to the generic except Exception as e handler, which returns:

"Error pulling repository: [Timeout was raised because the lock ... could not be acquired]"

This is confusing for users. The Timeout import suggests the intent was to handle it, but the handler was never written.

Fix: Catch Timeout explicitly and return a clear, actionable message:

except Timeout:
    return {
        'return': 1,
        'error': (
            f"Could not acquire lock for {repo_path} after 300 seconds. "
            "Another mlc process may be cloning or pulling this repo. "
            "Try again once the other operation completes."
        )
    }

Similarly for the repos.json lock in register_repo / unregister_repo.

🟢 Lock files are left on disk next to repo directories

FileLock creates <repo_path>.lock (e.g. mlcommons@mlperf-automations.lock) next to each repo directory in the repos folder, and never removes them. The files are harmless — repo listing reads from repos.json, not by scanning the directory. But users who manually browse ~/MLC/repos/ will see unfamiliar .lock files alongside their repos.

This is a cosmetic issue with no functional impact, but worth documenting in a comment near the lock construction so future maintainers don't mistake them for corruption.

🟢 PR template checklist vs documented branch policy

The PR checklist item Target branch is 'dev' is unchecked, and this PR correctly targets main. AGENTS.md states PRs should target main. The checklist template appears to be outdated — it's a template issue, not a code issue, but it's causing friction across PRs.


Summary

Area Status
Core lock design (FileLock, lock ordering) ✅ Correct
register_repo lock ✅ Correct implementation
unregister_repo lock ✅ Correct implementation
pull_repo clone-race lock ✅ Correct implementation
register_repo thread-safety test ❌ Tests a re-implementation, not the real method
Double-clone race test ❌ Missing entirely
Timeout handling ⚠️ Imported but never caught explicitly
Lock file litter 🟢 Cosmetic only

The locking logic itself is sound. The two test gaps are the main things to fix before merging.

@arjunsuresh

Copy link
Copy Markdown
Contributor

@copilot address the issue in the last comment

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Claude Code Review (requested by @anandhusooraj)

Reviewed by Claude (claude-sonnet-4-6) at the request of a maintainer....

Addressed all the issues raised:

  1. register_repo test now calls the real methodtest_concurrent_register_repo_no_data_loss creates real temp directories with proper meta.yaml files (unique UIDs) and calls RepoAction.register_repo directly. If the FileLock were removed from register_repo, the test would detect data loss and fail.

  2. Double-clone race test addedtest_concurrent_pull_repo_clone_called_once spawns N threads all calling pull_repo for the same absent repo URL with subprocess.run mocked. It asserts git clone is invoked exactly once.

  3. Timeout now caught explicitly in pull_repo, register_repo, and unregister_repo, each returning a clear, actionable error message.

  4. Comments added near the FileLock constructions in all three locations noting that the .lock files are left on disk intentionally and are harmless.

Commit: the latest two commits on this PR.

@arjunsuresh
arjunsuresh marked this pull request as ready for review July 19, 2026 10:28
@arjunsuresh
arjunsuresh requested a review from a team as a code owner July 19, 2026 10:28
@github-actions

Copy link
Copy Markdown

🤖 AI PR Review Summary\n\nThis PR introduces file locking using the filelock library to prevent concurrent modifications of critical files and directories during repo operations. It adds locking around updates to repos.json and around cloning/pulling git repositories. The design improves robustness against race conditions but introduces potential blocking and timeout errors. The force pull logic is enhanced to stash local changes before pulling and restore them after, with error handling for stash apply conflicts. Risks include deadlocks if locks are not released properly and complexity in error recovery during stash conflicts.

@github-actions

Copy link
Copy Markdown

🤖 AI PR Review Summary\n\nThis PR introduces file locking around critical file and directory operations to prevent concurrent access issues. It adds FileLock usage when reading/writing repos.json and when cloning or pulling git repositories. The locking improves robustness in multi-process scenarios but adds complexity and potential for deadlocks or timeouts. The git pull logic is enhanced to handle local changes with a forced stash and apply workflow, improving safety but increasing complexity and error handling paths. Overall, the design improves concurrency safety but requires careful testing of lock acquisition and stash conflict recovery.

@arjunsuresh
arjunsuresh changed the base branch from main to dev July 19, 2026 10:41
@arjunsuresh

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Resolved. The conflict was between the --shallow/--depth/--extra_git_args features added to origin/dev and the FileLock thread safety changes in this branch. I've merged both sets of changes in mlc/repo_action.py — the new features from dev are preserved, and the FileLock wrapping in pull_repo, register_repo, and unregister_repo is retained. All 10 tests pass. Commit: c8aa6f1

@arjunsuresh

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Resolved. The branch now includes all changes from the latest origin/dev (commits ed5706e and f1a71ba: extra_git_args validation, installer venv improvements) alongside the FileLock thread safety changes. All 10 tests pass.

@arjunsuresh

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request - there are 3 more conflicts w.r.t the dev branch

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request - there are 3 more conflicts w.r.t the dev branch

Resolved. Took origin/dev as the base for mlc/repo_action.py (which had the most recent features: extra_git_args validation, force pull improvements, apptainer support) and re-applied the FileLock thread safety wrapping on top:

  • FileLock import and Timeout handling preserved in all three locations (register_repo, unregister_repo, pull_repo)
  • All latest dev features retained
  • All 3 thread safety tests pass

Commit: the latest merge commit on this PR.

@github-actions

Copy link
Copy Markdown

🤖 AI PR Review Summary\n\nThis PR introduces file locking to prevent concurrent modifications of critical files and directories during repository operations. It adds a FileLock around the repos.json update to avoid race conditions when multiple processes modify the repo list, and similarly adds a FileLock around cloning and pulling operations on individual repositories to ensure exclusive access. The force pull logic with stash handling is preserved but now protected by the lock. Risks include potential deadlocks or delays if locks are held too long, and the lock files remain on disk but are harmless. The design improves concurrency safety but adds complexity and potential timeout errors that are handled with user-friendly messages.

@github-actions

Copy link
Copy Markdown

🤖 AI PR Review Summary\n\nThis PR introduces file locking around critical file and directory operations to prevent concurrent modifications, specifically when updating repos.json and when cloning or pulling git repositories. It uses the filelock library to acquire locks with timeouts, improving robustness in multi-process scenarios. The design improves concurrency safety but introduces risks of deadlocks or delays if locks are held too long. The force pull logic with git stash is preserved but now wrapped inside the repo lock. Some error handling and logging are enhanced. Overall, the changes are positive but require careful testing under concurrent usage to avoid lock contention issues.

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.

Make mlc pull repo thread safe

3 participants