4.0.0:
- Fixed
open_atomic()replacing a destination created while its context
was open on POSIX; publication now raisesFileExistsErrorand preserves
the concurrent winner (#114) - Python 3.10 or later is now required; Python 3.9 (EOL) support dropped
pywin32is no longer installed by default on Windows; the msvcrt-based
locker is the default and works dependency-free for exclusive locks.
Shared locks on Windows requireportalocker[win32], and an informative
ImportErroris raised otherwise (#104)- POSIX lock exceptions now populate
.strerrorand pass the message as
a second positional argument, matching the Windows exception contract.
This changes thestr()output of these exceptions on POSIX from the
bareOSErrortext to a 2-tuple repr LockBaseis now generic over the acquire return type (typing-only
change; downstreamLocksubclasses are unaffected)- Added
PidFileLockfor pidfile-based locking (#106) - Added
PidFileLock.fail_closed()for ownership-only contexts; contention
raisesAlreadyLockedbefore entering the body and exposes the competing
PID throughAlreadyLocked.holder_pidwhen readable (#118) - Packaging switched to the
uv_buildbackend; releases are published to
PyPI through GitHub Actions Trusted Publishing python -m portalocker combine:--output-filenow opens lazily;
the vendored single-file output correctly includesRedisLock(it was
alwaysNonebefore); the smoke-run now usessys.executable
(fixes Windows)- Fixed
PidFileLockignoringtimeoutwhenfail_when_locked=False;
all PID-publication failures now transactionally release the sidecar and
preserve the publication error if cleanup also fails (#116) - Fixed a
TemporaryFileLock/PidFileLockunlock-then-unlink race that
could let two processes hold the same lock (split-brain); release now
unlinks before unlocking on POSIX and acquire re-verifies file identity
(inode) after locking (#115) - Fixed
BoundedSemaphorestaying permanently "Already locked" after a
non-contention error (e.g. a missing directory) - Fixed
RedisLockcrashed-holder detection: the liveness check was
always satisfied by its own subscribe confirmation, so dead holders were
never reaped; the ping is now published only after the subscription is
confirmed active, pubsub connections no longer leak on the reap path,
fail_when_locked=Truefails fast instead of polling the full timeout,
internally-created connections are closed on release, and a failed
acquire()rolls back cleanly so the lock can be retried - Added shared
RedisLockreaders throughLockFlags.SHARED. Waiting
writers gate new readers to prevent starvation, holder-specific heartbeats
preserve stale-client cleanup, and legacy Redis lock responses are treated
as exclusive for mixed-version safety (#124) - Fixed
FlockLocker/LockfLockeron POSIX to use their named syscall
(previously silently used the globalLOCKER); the module-level
lock()/unlock()on POSIX now accept all documentedLOCKER
forms (tuple/instance/class) - Fixed the Windows msvcrt locker locking from the current file position
instead of byte 0 for raw file descriptors, which could break mutual
exclusion on files larger than 64KiB; unexpected Win32 errors now raise
LockExceptionper the documented contract RedisLocktests now run everywhere viafakeredis, with a live
Redis server still tested in CILock.release()continues suppressing unlock and close errors by default;
closing is always attempted and the file handle reference is cleared.
Callers can opt into reporting cleanup failures with
Lock(..., raise_on_release_error=True)(#117)TemporaryFileLock.release()andPidFileLock.release()are now
no-ops when the object does not hold the lock, so a stale object (double
release, or garbage collection of a failed acquire) can no longer unlink
the lock file out from under the current holderTemporaryFileLockno longer keeps a strongatexitreference to
itself, so unused instances can be garbage collected; cleanup at
interpreter exit still happens via a weak referenceNamedBoundedSemaphoreis now exported from the top-level
portalockernamespace