Skip to content

refactor(locks): delegate cross-platform file locking to portalocker - #100

Merged
KylinMountain merged 3 commits into
mainfrom
fix/locks-portalocker
Jun 15, 2026
Merged

refactor(locks): delegate cross-platform file locking to portalocker#100
KylinMountain merged 3 commits into
mainfrom
fix/locks-portalocker

Conversation

@KylinMountain

@KylinMountain KylinMountain commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replace the hand-rolled fcntl/msvcrt flock/funlock added in #99 with portalocker — fcntl-backed on POSIX, msvcrt/Win32-backed on Windows, with maintained, cross-platform-tested behaviour.

Motivation: the #99 Windows fallback was hand-written msvcrt logic (retry loop, timeout, byte-range locking) that could not be exercised on POSIX, so its Windows behaviour was unverified. Delegating to a vetted library moves the untestable platform path to code its maintainers test on Windows.

Changes

  • flock/funlock now call portalocker.lock / portalocker.unlock.
  • Drop the guarded import fcntl, the msvcrt fallback, and _WINDOWS_LOCK_TIMEOUT / retry loop.
  • Keep the os.fchmod guard and the Windows directory-fsync skip (atomic writes — not covered by portalocker).
  • Pin portalocker==3.2.0 (BSD-3) to match the exact-pin dependency policy. portalocker declares pywin32 under sys_platform == 'win32', so it is installed automatically on Windows (no transitive dep on POSIX).

Windows behaviour (verified against portalocker 3.2.0 source)

  • Shared (reader) locks ARE honoured on Windows — portalocker uses the Win32 LockFileEx API (via the auto-installed pywin32) for shared locks, so concurrent readers work. (Correcting an earlier draft of this PR that wrongly said shared locks need a manual pywin32 add.)
  • Exclusive locks on Windows use msvcrt.locking, which retries ~10s then raises rather than blocking indefinitely. A concurrent exclusive waiter contending with a long-held lock fails fast (LockException) instead of queueing — acceptable for OpenKB's usage; documented in the flock docstring.
  • Lock-acquire failures raise portalocker.LockException (not OSError).

Testing

Supersedes the hand-rolled approach in #99. Refs #93.

Replace the hand-rolled fcntl/msvcrt flock/funlock (merged in #99) with
portalocker, which is fcntl-backed on POSIX and msvcrt/Win32-backed on Windows
with maintained, cross-platform-tested behaviour. Removes the hand-written
Windows retry/timeout loop that could not be exercised on POSIX.

- flock/funlock now delegate to portalocker.lock/unlock.
- Drop the guarded 'import fcntl', the msvcrt fallback, and _WINDOWS_LOCK_TIMEOUT.
- Keep the os.fchmod guard and Windows directory-fsync skip (atomic writes,
  which portalocker does not cover).
- Pin portalocker==3.2.0 (BSD-3) to match the exact-pin dependency policy.

Note: true shared (reader) locks on Windows would still need pywin32; without
it portalocker uses msvcrt (exclusive). Not added — in-process concurrent KB
reads are rare. Refs #93.

Tests: swap the msvcrt-internals tests for a cross-process exclusion test that
verifies flock takes a real OS lock, plus the retained atomic-write/fsync-skip
guards. Full suite 749 passed.
Comment thread tests/test_cross_platform_locks.py Fixed
…rdening

Addresses /code-review findings on the portalocker refactor:

- flock docstring corrected: portalocker uses Win32 LockFileEx (pywin32, pulled
  in automatically on Windows) for SHARED locks, so concurrent readers ARE
  honoured; EXCLUSIVE uses msvcrt (retries ~10s then raises, not an infinite
  block); failures raise portalocker.LockException, not OSError.
- Re-add the issue #93 regression guard: assert no openkb module hard-imports
  the Unix-only fcntl at module level (replaces the dropped import-without-fcntl
  test without depending on portalocker internals).
- Strengthen the cross-process lock test: assert both BLOCKED (while held) and
  ACQUIRED (after release), and check the probe's exit code so an ImportError
  surfaces clearly instead of an empty-stdout false failure.
- Drop the now-dead 'import pytest' / 'import portalocker' from the test module.
Comment thread tests/test_cross_platform_locks.py Fixed
Drop the unused 'import openkb' and derive the package dir from
locks.__file__ instead, so the test module uses one import style for openkb
(github-code-quality bot). The unused 'import portalocker' was already removed
in the prior commit.
@KylinMountain
KylinMountain merged commit 293783b into main Jun 15, 2026
1 check passed
@KylinMountain
KylinMountain deleted the fix/locks-portalocker branch June 22, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant