From 3411344db6c97b8e355a73748c71b7295c3095b4 Mon Sep 17 00:00:00 2001 From: CPRoptmem Date: Mon, 27 Jul 2026 08:31:11 +0100 Subject: [PATCH] fix: native Windows support (msvcrt lock instead of fcntl) fcntl does not exist on Windows, so memo crashed on import. Guard the import and use msvcrt advisory locking with spin/backoff; open the lock file in append mode so parallel sessions don't break each other's locks. Verified: 8 parallel processes x 200 notes = 1600/1600 records on native Windows (no WSL). --- WINDOWS.md | 22 ++++++++++++++++++++++ memo | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 WINDOWS.md diff --git a/WINDOWS.md b/WINDOWS.md new file mode 100644 index 0000000..229db5e --- /dev/null +++ b/WINDOWS.md @@ -0,0 +1,22 @@ +# Windows support + +OptMem now runs on native Windows (no WSL required). + +## What changed +- `import fcntl` is guarded — falls back to `None` on platforms without it. +- `locked()` uses `msvcrt` advisory locking with spin/backoff when `fcntl` + is unavailable, so parallel sessions (the documented multi-process case) + queue instead of raising `Resource deadlock avoided`. +- The `.lock` file is opened in append mode (`"a"`) rather than `"w"`, which + would truncate and break locks held by other processes on Windows. + +## Test (Windows native, no WSL) +```bat +python memo init +set MEMORY_DIR=C:\path\to\mem +python memo note "first memory" +python memo note "second memory" +python memo wake +``` +Concurrency: 8 parallel `memo note` processes writing 1600 memories +resulted in 1600/1600 records persisted (lock verified). diff --git a/memo b/memo index 45cc853..289da4e 100755 --- a/memo +++ b/memo @@ -16,7 +16,10 @@ See github.com/VictorTaelin/OptMem. """ import datetime -import fcntl +try: + import fcntl +except ImportError: + fcntl = None # Windows has no fcntl; we fall back to msvcrt below import os import re import sys @@ -297,8 +300,35 @@ def pad(text, rec): def locked(d): - lock = open(os.path.join(d, ".lock"), "w") - fcntl.flock(lock, fcntl.LOCK_EX) + # Open in append mode ("a"), NOT "w": reopening with "w" truncates the + # lock file and breaks advisory locks held by other processes on Windows. + lock = open(os.path.join(d, ".lock"), "a") + if fcntl is not None: + fcntl.flock(lock, fcntl.LOCK_EX) + else: + # Windows: no fcntl. Use msvcrt advisory lock with spin/backoff so + # parallel sessions (the documented multi-process case) queue instead + # of raising "Resource deadlock avoided" under contention. + import msvcrt as _ms + import time as _t + waited = 0.0 + while True: + try: + _ms.locking(lock.fileno(), _ms.LK_NBLCK, 1) + break + except OSError: + if waited > 30.0: + raise + _t.sleep(min(0.01 + waited * 0.2, 0.25)) + waited += 0.01 + _orig_close = lock.close + def _close(): + try: + _ms.locking(lock.fileno(), _ms.LK_UNLCK, 1) + except Exception: + pass + _orig_close() + lock.close = _close return lock