Skip to content

fix: make experimental file locks importable on Windows#1927

Open
JWilksBooth wants to merge 1 commit into
PrimeIntellect-ai:mainfrom
JWilksBooth:fix/windows-fcntl-compat
Open

fix: make experimental file locks importable on Windows#1927
JWilksBooth wants to merge 1 commit into
PrimeIntellect-ai:mainfrom
JWilksBooth:fix/windows-fcntl-compat

Conversation

@JWilksBooth

@JWilksBooth JWilksBooth commented Jul 4, 2026

Copy link
Copy Markdown

verifiers.envs.experimental.utils.file_locks and .git_checkout_cache import fcntl unconditionally, so importing verifiers (and therefore vf.load_environment and the entire vf-eval CLI) crashes on Windows with "ModuleNotFoundError: No module named 'fcntl'", surfaced misleadingly as a prompt to install verifiers[all].

Description

Guard the fcntl import and fall back to msvcrt byte-range locking on Windows:

  • Shared locks degrade to exclusive locks (msvcrt has no shared locks) — strictly more conservative, preserves the worktree pruning contract.
  • Contended nonblocking acquisitions raise BlockingIOError, matching flock(LOCK_EX | LOCK_NB) semantics, so _prune_stale_worktrees keeps skipping in-use checkouts instead of crashing (msvcrt raises bare OSError on contention, which the pruning code doesn't catch — the fallback translates it).
  • No behavior change on POSIX; the fcntl paths are untouched.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Test improvement

Testing

Verified on Windows 11 / Python 3.10.9 against this branch's modules loaded by path:

  • exclusive_file_lock acquire / release / re-acquire; shared_file_lock; shared/exclusive_path_lock (nonblocking)
  • Cross-process contention: second process holding the lock → nonblocking acquisition raises BlockingIOError as the pruning code expects
  • git_checkout_cache._acquire_in_use_lock / _release_all_in_use_locks (process-lifetime lock, idempotent re-entry)
  • Reproduced the original crash against a pristine 0.1.14 wheel on the same machine; with this patch the modules import and locks work
  • py_compile on both files. Full uv run pytest not run locally (Windows box; POSIX paths untouched and byte-identical in behavior).

Checklist

  • My code follows the style guidelines of this project as outlined in AGENTS.md
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (N/A — no user-facing docs cover these internals)
  • My changes generate no new warnings
  • Any dependent changes have been merged and published (N/A)

Additional Notes

  • verifiers/v1/runtimes/limiters.py also has a module-level import fcntl, but only loads with the v1 prime/modal runtimes (off the default import path) — left untouched; happy to guard it the same way if wanted.
  • Known related Windows issue, not addressed here: the ZMQ env server never becomes healthy on Windows, so vf-eval needs --disable-env-server. With that flag plus this patch, vf-eval runs end to end on Windows.

Note

Medium Risk
Changes cross-process locking semantics on Windows (shared locks become exclusive) in git checkout pruning and experimental env utilities; POSIX behavior is untouched but concurrency contracts differ on the new platform.

Overview
Fixes Windows import crashes (ModuleNotFoundError: fcntl) for verifiers.envs.experimental.utils.file_locks and git_checkout_cache, which blocked vf.load_environment and the eval CLI on that platform.

file_locks.py now probes fcntl at import time and, when missing, uses msvcrt byte-range locking via a new _msvcrt_file_lock helper wired into shared_file_lock and exclusive_file_lock. On Windows, shared locks become exclusive (no shared mode in msvcrt), which the PR treats as stricter but safe. Nonblocking contention is normalized to BlockingIOError, matching flock(LOCK_NB) so callers like _prune_stale_worktrees keep skipping in-use checkouts instead of failing on bare OSError.

git_checkout_cache.py applies the same _HAVE_FCNTL guard for process-lifetime in-use locks in _acquire_in_use_lock, using msvcrt.LK_LOCK when fcntl is unavailable. POSIX paths are unchanged when fcntl imports successfully.

Reviewed by Cursor Bugbot for commit 0c30716. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix file lock imports in experimental utils to work on Windows

  • Replaces unconditional fcntl imports in file_locks.py and git_checkout_cache.py with try/except blocks that fall back to msvcrt on Windows, gated by a _HAVE_FCNTL flag.
  • Adds a _msvcrt_file_lock context manager that implements byte-range locking via msvcrt.locking, with blocking and nonblocking modes and BlockingIOError on contention to mirror fcntl semantics.
  • Behavioral Change: on Windows, both shared_file_lock and exclusive_file_lock use exclusive 1-byte locks since msvcrt has no shared lock primitive.

Macroscope summarized 0c30716.

verifiers.envs.experimental.utils.file_locks and .git_checkout_cache
import fcntl unconditionally, so importing verifiers (and therefore
vf.load_environment and the entire vf-eval CLI) crashes on Windows with
"ModuleNotFoundError: No module named 'fcntl'", surfaced misleadingly
as a prompt to install verifiers[all].

Guard the fcntl import and fall back to msvcrt byte-range locking on
Windows:

- Shared locks degrade to exclusive locks (msvcrt has no shared
  locks), which is strictly more conservative and preserves the
  worktree pruning contract.
- Contended nonblocking acquisitions raise BlockingIOError, matching
  flock(LOCK_EX | LOCK_NB) semantics, so _prune_stale_worktrees keeps
  skipping in-use checkouts instead of crashing.

Verified on Windows 11 / Python 3.10.9: lock acquire/release/
reacquire, cross-process nonblocking contention, and the
process-lifetime in-use lock in git_checkout_cache.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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