Repository navigation
Support Windows: portable advisory locking and itimer-free hook timeout - #54
volkangulecx wants to merge 1 commit into
Conversation
agent-memory lists Windows as a supported platform but fails to import and run there. Two POSIX-only dependencies are loaded at import/run time: - fcntl (flock advisory locking in core/locking.py and core/observation.py) raises ModuleNotFoundError at import, so the whole package fails to load. - signal.setitimer / signal.SIGALRM (the hook timeout watchdog in adapters/hook_entry.py) raises AttributeError when mem-hook fires. Add core/portlock.py: an exclusive advisory lock that delegates to fcntl.flock on POSIX and msvcrt.locking on Windows, surfacing contention as BlockingIOError on both so call sites keep catching one exception type. locking.py and observation.py call it instead of fcntl directly. Make hook_entry._arm/_disarm no-ops where itimer signals are unavailable; the host's own hook timeout still bounds the call. POSIX behavior is unchanged: the lock is still advisory and per-store and still uses flock. Files remain the single source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Ran this on real Windows 11 (Python 3.13.5). Everything below is measured on this machine, not inferred, except where marked. The one thing I did not test is a long-lived contention storm, only bounded acquisition(长时间高争用场景我没实测过). The six new tests: five pass, test_arm_disarm_round_trip_with_itimer skips on win32 as designed. Full tests/unit/ against a comparison baseline of main run with an fcntl shim (main cannot even import on Windows without one): main gives 471 passed / 30 failed, this branch gives 480 passed / 26 failed. The failure-set diff is strictly positive: zero tests that fail here but pass on main, and four hook/muse tests that fail on main now pass (the itimer no-op paths doing their job). Cross-process exclusion, the property store_lock actually needs: parent holds the lock, a spawned child's non-blocking acquire gets BlockingIOError; after unlock, the child acquires. Verified. Two non-blocking notes. _arm on Windows is a no-op, so mem-hook loses its timeout guard entirely there rather than getting a replacement — the PR title's "itimer-free hook timeout" reads like an alternative mechanism exists, worth one sentence in the description saying the guard is disabled on Windows (and maybe a TODO for a thread-based timer later; this reading of the title is 仅个人判断). And 26 unit tests still fail on Windows for reasons this PR does not touch: scoped-recall path separators (forward-slash scopes never match backslash paths), hook setup writing POSIX-style absolute commands, a couple of timestamp cases. All of them fail identically on the main baseline, so they are pre-existing gaps, not regressions — but a follow-up would keep "Windows supported" honest end to end. Net: this unblocks importing and using the store on Windows outside those edges, with no regression I could find. Filing the follow-up issue with the full 26-test list next. |
What
agent-memory fails to import and run on Windows today, although the README lists Windows as a supported platform. Two POSIX-only dependencies are loaded at import/run time:
fcntl(used forflockadvisory locking incore/locking.pyandcore/observation.py) — raisesModuleNotFoundErrorat import, so the entire package fails to load.signal.setitimer/signal.SIGALRM(the hook timeout watchdog inadapters/hook_entry.py) — raisesAttributeErrorwhenmem-hookfires.This PR makes both portable without changing POSIX behavior.
How
core/portlock.py: an exclusive advisory lock that delegates tofcntl.flockon POSIX andmsvcrt.lockingon Windows, surfacing contention asBlockingIOErroron both platforms so call sites keep catching one exception type.locking.pyandobservation.pycall it instead of importingfcntldirectly.hook_entry._arm/_disarmdegrade to no-ops wheresignal.setitimer/SIGALRMare unavailable (Windows). The host's own hook timeout still bounds the call.Invariants preserved: files stay the single source of truth, the lock stays advisory and per-store, and POSIX uses
flockexactly as before.Tests
tests/unit/test_portlock.py— lock round-trips (blocking and non-blocking), accepts a file object or a raw descriptor, preserves the descriptor offset, and the lock-holding modules import on every platform (the Windows regression).tests/unit/test_hook_timeout_guard.py— the watchdog is a no-op without itimer and round-trips with it.ruff check,mypy, and the existingtest_locking.pypass. Verified end to end on Windows 11:mem init/record/recall/rebuild, and the Claude CodeSessionStarthook injectingMEMORY.md.Out of scope / known follow-up
On Windows,
tests/system/test_read_observability.py::test_host_transcript_limits_and_failurestill fails: it asserts a0o600file mode, which POSIX permission bits do not express on Windows. That is independent of locking and signals and is left for a separate change.