# What changed in portalocker

> 114 changes in portalocker 3 to 4 from its official release notes, upgrade guides and advisories: 30 breaking, 5 deprecations, 2 security, 20 new APIs and options. Each one cited. Sources checked 2026-10-29. HTML: https://unstale.sh/pypi/portalocker/

To see which of these apply to a repository, run `npx @unstale/cli init` in it.

## portalocker 4

### Breaking changes

- Reacquiring a semaphore instance that already holds a slot now raises LockException instead of AssertionError. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Lock resolves its path with os.path.abspath at construction, so filename now contains an absolute path and later chdir calls do not redirect lock-file operations. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Flags are validated on every platform before system calls; UNBLOCK, SHARED | EXCLUSIVE, and flags naming no lock type raise RuntimeError. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- On POSIX, modes containing w now clear O_APPEND after deferred truncation, so positioned writes behave like builtin open(mode='w'). Windows append semantics remain. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- An exception from the with-block now takes precedence over a release error, which is chained as its context. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Only AlreadyLocked contention is retried. Other LockException failures now raise immediately, and custom lockers must raise AlreadyLocked for contention to be retried. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The finalizer that released locks and closed filehandles when a wrapper was collected has been removed, restoring the 3.2.0 semantics. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Deleting a lock stored as a class attribute now releases the lock itself instead of calling release on its owner. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Fallback LK_* values now match the system constants, and are stored on the locker instance rather than added to the shared msvcrt module. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Release failures no longer mask the with-body exception; they are chained to it, and release errors follow raise_on_release_error. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Plain LockException failures from the sidecar are no longer normalized to AlreadyLocked; terminal backend failures propagate as themselves. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- PID parsing now accepts only plain positive ASCII decimal values; other inputs are reported as unreadable. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Context entry now raises AlreadyLocked when another process holds the lock but its PID is missing, unreadable, or invalid, instead of returning None. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- An existing destination now raises FileExistsError on entry instead of AssertionError. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Legacy universal-newline U mode strings have been removed from the Mode literal, narrowing the static typing contract without changing runtime behavior. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The subscription uses a dedicated connection that never retries or reconnects; holders on flaky networks lose their locks rather than silently retaining an invalid hold. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- A clean with-block exit raises LockLostError after releasing if the lock was lost; release itself does not raise due to loss. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Acquiring an instance that already holds a lock raises portalocker.LockException instead of AssertionError. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Forked children release only local references to inherited held locks rather than unsubscribing and releasing the parent's lock; a child needing a lock must create its own instance. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Worker failures are scoped to the affected lock: waiter subscription failures retry within the timeout, while a held lock's worker death marks it lost. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Without the optional Redis dependency, RedisLock is now a stub class whose constructor raises ImportError with the dependency and install command; callers should catch ImportError rather than compare it with None. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- An inconsistent held-count state with no filehandle now raises LockException instead of relying on an assert that python -O removes. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Reacquiring a valid held lock is an idempotent no-op; if its path was externally unlinked or replaced, acquisition raises LockException without closing the held handle. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- A failing POSIX module-level unlock raises portalocker.LockException instead of raw OSError; the original error remains available through args[0] and __cause__. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- On POSIX, publication now raises FileExistsError and preserves a destination created while the context was open. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Windows shared locks require this optional extra; otherwise an informative ImportError is raised. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Exceptions now populate .strerror and pass the message as a second positional argument, matching the Windows contract; their str() output changes to a 2-tuple repr. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Python 3.10 or later is required; Python 3.9 support has been dropped. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- No longer installed by default on Windows; exclusive locks use the dependency-free msvcrt-based locker, while shared locks require portalocker[win32]. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)

### Security

- Promotion confirmation and election changes prevent competing writers from becoming simultaneous exclusive holders; mixed-version protection is complete once all channel writers run 4.2 or later. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The unlock-then-unlink race is fixed: POSIX release unlinks before unlocking, and acquire re-verifies file identity after locking. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)

### Deprecations

- Passing a bool as check_interval is still honored but now warns; it raises TypeError in 5.0. (4.4.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.4.0)
- Passing a bool as timeout is still honored but now warns; it raises TypeError in 5.0. (4.4.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.4.0)
- The default-name DeprecationWarning now points to the constructing caller and deduplicates per call site. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Deprecated because the package has never raised this exception; instantiating it now emits DeprecationWarning. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Deprecated because its reap arm can kill live-but-slow holders; use the read-only probe() to inspect channel participants. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)

### New APIs and options

- Adds the BinaryMode alias, used to drive the filehandle typing overloads. (4.3.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.0)
- Adds the TextMode alias, used to drive the filehandle typing overloads. (4.3.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.0)
- RedisLock provides a check for verifying ownership during long critical sections. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Exposes the token for a fenced exclusive grant; it is None without a fenced grant and survives loss and release until the next acquire. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Opt-in fencing increments a never-expiring channel fence key after exclusive-grant confirmation and provides a token for downstream resources to reject stale writes. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Controls whether loss interrupts the main thread; it defaults to True in 4.2, warns when unset, and is scheduled to default to False in 5.0. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Lock exceptions preserve the file name as a string in fh_name; pickling and deep-copying drop the file handle while shallow copies share it. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- RedisLock exposes whether its lock has been lost. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- A callback runs exactly once per loss on the keep-alive thread; it may call release() on the lock. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- A new exception reports RedisLock loss and carries channel, holder_id, and the causal exception as __cause__. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- A read-only method reports who is on the channel without side effects. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The exception raised by a failed self-check becomes the cause of LockLostError. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Opt-in self-checks publish a liveness ping at each interval and require a response within the configured interval or unavailable_timeout, surfacing failures as lock loss. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- RedisLock accepts a factory for deriving its dedicated subscription connection, supporting exotic setups such as Sentinel, cluster, and custom pools. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Exposes the competing PID when it is readable. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Cleanup failures remain suppressed by default, but callers can opt into reporting them; closing is always attempted and the file handle reference is cleared. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- NamedBoundedSemaphore is now exported from the top-level portalocker namespace. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Adds ownership-only contexts; contention raises AlreadyLocked before entering the body. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Adds pidfile-based locking. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Adds shared readers; waiting writers gate new readers to prevent starvation, holder-specific heartbeats preserve stale-client cleanup, and legacy responses are treated as exclusive for mixed-version safety. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)

### Performance

- Probe reply draining collects in-flight and buffered replies in the same pass, sharply reducing probe duration and staleness windows. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)

### Behavior and default changes

- Lock is generic over its filehandle type. Literal text and binary modes infer IO[str] and IO[bytes]; a bare Lock defaults to Lock[IO[str]], matching the default mode. (4.3.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.0)
- RLock is generic over its filehandle type. Literal text and binary modes infer IO[str] and IO[bytes]; a bare RLock defaults to the text filehandle type. (4.3.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.0)
- A second acquire on an instance that already holds the lock is now an idempotent no-op that does not touch the sidecar lock or PID file. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Publication falls back to an existence check and rename on filesystems without hard-link support; the fallback cannot reliably refuse concurrent publishers. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- With fail_when_locked, acquisition raises only after a conclusive probe; inconclusive probes retry within timeout, and timeout=0 preserves strict single-attempt behavior. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Verification retries now share one deadline and receive only the remaining timeout budget. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The --output-file option now opens lazily. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- With fail_when_locked=True, acquisition fails fast instead of polling for the full timeout. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- No longer keeps a strong atexit reference to itself, allowing unused instances to be garbage collected while interpreter-exit cleanup continues via a weak reference. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)

### Tooling

- The build backend requirement is now uv_build>=0.11,<1, allowing other build frontends and distribution packagers to build the sdist. (4.4.2) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.4.2)

### Fixes and other changes

- Anchors a relative destination when the context is entered, so changing the working directory inside the body no longer causes FileNotFoundError or leaves the temporary file behind after a failing body. The yielded handle name and FileExistsError paths are now… (4.4.1) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.4.1)
- Cancellation during setup or between attempts releases resources, preserves the original exception, and permits reuse. (4.3.1) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.1)
- Polling retains a final read so buffered holder responses are processed before deciding that a holder is unavailable. (4.3.1) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.1)
- Retry sleeps are capped at the acquisition deadline, so a large check_interval no longer adds a full interval after timeout. (4.3.1) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.1)
- Lock now accepts subscription at runtime, allowing expressions such as Lock[typing.IO[bytes]] that raised TypeError in 4.2.0. (4.3.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.0)
- RLock.__init__ now initializes through Lock._init instead of Lock.__init__; monkeypatched constructors will notice. (4.3.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.0)
- RLock now accepts subscription at runtime. (4.3.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.0)
- Its filehandle type is pinned to IO[str]. (4.3.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.3.0)
- Lock.acquire with fail_when_locked now propagates the locker's arguments so strerror is populated, while the original error remains in args[0] and __cause__. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Concurrent acquisition on one instance no longer leaks a slot: a losing thread returns its extra slot and receives LockException. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The blocking-mode timeout warning now fires at most once per lock instance and points to the caller, including through subclass constructors. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Concurrent acquire calls on one Lock instance are documented as unsupported; use one instance per thread because POSIX record locks are process-wide. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Failed acquisition paths, including interrupts, now unlock and close opened descriptors before returning or propagating; PidFileLock rolls back its sidecar too. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- If file preparation fails after the lock is taken, the handle is now unlocked and closed before the original error escapes. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Concurrent or reentrant releases atomically claim lock state so only one caller tears the lock down. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Sleep intervals are capped by the remaining deadline, preventing acquisition from exceeding its timeout by up to a full check_interval. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Exceptions carrying open file handles can now cross multiprocessing boundaries without failing to pickle; detached handles whose name lookup fails no longer break exception construction. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Interpreter-exit cleanup now releases only locks constructed by the exiting process, avoiding removal of lock files inherited by a forked child. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- PID publication now writes a neighboring temporary file and replaces the destination, so readers see either the old complete PID or the new complete PID. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Per-call timeout and check_interval arguments are now coalesced with instance attributes before constructing the sidecar Lock. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The sidecar reference is published only after successful acquisition; interrupted attempts roll back and release refuses to unlink files when the sidecar has no held handle. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- PID files are read as bytes and validated as ASCII digits; undecodable content and digit runs longer than 4300 digits return None. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- On Windows, the PID file is now unlinked before releasing the sidecar lock, preventing cleanup from deleting a successor's newly published PID. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The temporary file is created with mode 0o666 so the process umask determines destination permissions as with plain open. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Failed publication now keeps the temporary file and names its path in the exception; exceptions from the caller's body remove the temporary file, and closing the handle in the body no longer breaks publication. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- The command now reads and assembles inputs before opening --output-file, preventing malformed input from truncating an existing output. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- TemporaryFileLock and PidFileLock constructors now accept and forward this documented keyword; PidFileLock follows it when handling unlink errors. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Acquisition failures now clean up subscriptions and workers before propagating non-retryable errors, leaving the instance reusable. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- RedisLock acquisition now waits for subscription confirmation, serializes promotions and ping answers, and checks subscriber counts again before pinging, closing races that could let two writers hold the lock. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Acquire counts and their transitions are protected by the instance state lock, preventing lost increments and handle closure while a hold remains outstanding. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- A single module-level exit hook now releases held locks, replacing one never-unregistered callback per constructed instance. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Release errors now honor raise_on_release_error=False on teardown and are logged at warning level instead of disappearing. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- TemporaryFileLock and PidFileLock now annotate filename as types.Filename, allowing pathlib.Path arguments to pass static type checking. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- Each LockFileEx and UnlockFileEx call now receives a fresh OVERLAPPED structure instead of reusing a cached instance across calls and threads. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- OSError from the Windows lock path is now wrapped in LockException, matching the unlock path. (4.2.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.2.0)
- No longer stays permanently Already locked after a non-contention error, such as a missing directory. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- On POSIX, now uses its named syscall instead of silently using the global LOCKER. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- The module-level POSIX lock() and unlock() now accept all documented LOCKER forms: tuple, instance, or class. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- LockBase is now generic over the acquire return type; downstream Lock subclasses are unaffected. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Unexpected Win32 errors now raise LockException, matching the documented contract. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Windows locking now starts at byte 0 for raw file descriptors, preserving mutual exclusion on files larger than 64KiB; unexpected Win32 errors raise LockException. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- The timeout is no longer ignored when fail_when_locked=False. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- PID-publication failures now transactionally release the sidecar and preserve the publication error if cleanup also fails. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Vendored single-file output now correctly includes RedisLock instead of always producing None. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Fixes crashed-holder detection and cleanup: the liveness ping follows active subscription confirmation, pubsub connections do not leak on reap, internally created connections close on release, and failed acquisition rolls back for retry. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)
- Release is a no-op when the object does not hold the lock, preventing a stale object from unlinking the current holder's lock file. (4.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v4.0.0)

## portalocker 3

### Breaking changes

- Support for deprecated Python versions was dropped; only Python 3.9 and up are supported. (3.0.0) — [release notes](https://github.com/wolph/portalocker/releases/tag/v3.0.0)
