Changelog

Unreleased (2026-10-27)

Packaging

  • Releases now run on the shared tox-dev/github-workflows pipeline, with a locked release toolchain and generated GitHub Release notes.

4.2.4 (2026-10-19)

4.2.3 (2026-10-19)

Fixed

  • Creating a lock on a Python 3.15 interpreter without types.LazyImportType no longer triggers AttributeError at exit. PR #861 - by @gaborbernat.

  • Handle EFTYPE when a no-follow open refuses a symlink in a soft reader/writer lock’s protocol tree, so heartbeat reports lost ownership rather than a transient refresh error. PR #859 - by @edsonsims.

4.2.2 (2026-10-18)

Fixed

  • Fix a lock on a relative path that stayed held after a later with lock: ended, when an earlier acquire() had failed because the working directory no longer exists. That call still raises FileNotFoundError, and it now leaves the lock’s hold count unchanged. PR #856 - by @spicysharkco.

4.2.1 (2026-10-17)

Fixed

  • On a singleton wrapper, AsyncReadWriteLock.close() and AsyncSoftReadWriteLock.close() end only the holds that tasks acquired through that wrapper. A reader that arrives during close() gets the closed error instead of joining the ending hold. After synchronous code closes or force-releases the shared lock, the wrappers refuse to admit readers into the ended hold, and close() on such a wrapper returns. Closing one wrapper no longer ends a task’s nested hold taken through another wrapper on the same path. PR #832 - by @gaborbernat.

  • ReadWriteLock locks again after a release() that an exception from a signal handler interrupted. The async read-write wrappers stop admitting readers into such a level. The async read-write locks forget a hold whose backend release failed after the backend let go, and they free the memory of each acquire that times out or is canceled. AsyncFileLock returns from release() after a few hundred acquires gave up behind a waiting one. An exception from a signal handler delivered while an AsyncFileLock call joins its queue no longer blocks every later call on that lock. PR #833 - by @gaborbernat.

  • Fix ReadWriteLock acting on -journal, -wal or -shm files another process puts beside its database: a write could land in another file, a reader could get in beside a writer, and an acquire could fail. SQLite now opens the database by name only in a directory no other account can write, and through a private hard link elsewhere. On Windows the directory’s access control list decides. Where hard links are unavailable, keep the database in a directory other accounts cannot write, such as one under %LOCALAPPDATA%. The lock removes link directories through a checked descriptor rather than by name, reads a link directory’s name only when the locking account owns it, and leaves alone a directory with other content renamed onto a link directory’s name. A database whose schema takes more than a few kilobytes to load raises ValueError instead of running past the acquire timeout. PR #834 - by @gaborbernat.

  • Fix five bugs in opening the lock file. UnixFileLock no longer truncates a file it did not create, such as one renamed to the lock path, and no longer applies mode to it. An on_acquired hook that writes holder metadata now truncates the file itself. A lease (Linux) or an oplock (Windows) on the lock file or a soft lock’s marker no longer holds acquire() past its timeout or blocking=False, and an oplock no longer holds release(); ReadWriteLock on Windows waits for its timeout and raises Timeout. On Windows, a mode without group and other bits, such as 0o600, now creates the lock file with an owner-only permission list; the ReadWriteLock database gets the same. The soft locks’ writability check no longer follows a symlink swapped in at the lock path. On a filesystem without flock, a contender from another account now waits on a marker it cannot write instead of raising PermissionError. PR #835 - by @gaborbernat.

  • Fix SoftFileLock, MarkerSoftFileLock and SoftFileLease removing a live holder’s marker in three cases: the holder and the contender run in Linux time namespaces with different boottime offsets; only one of them can read the boot id; a process other than the owner rewrote the marker. Soft markers no longer carry group or other write bits. A process name containing a carriage return or another line separator no longer makes the stale check raise ValueError. When /proc/self/ns/time exists but cannot be read, the stale check no longer compares start times and keeps the marker. PR #836 - by @gaborbernat.

  • The async read-write locks record their hold under a task owner, so synchronous code on the executor thread that took the hold can no longer re-enter or release it. AsyncSoftReadWriteLock gives back one level on release, so synchronous readers of a shared singleton keep theirs. SoftReadWriteLock tracks read levels per thread: release() from a thread that holds none raises RuntimeError. After its heartbeat recorded a lost hold, it refuses new acquires until that hold is released. PR #838 - by @gaborbernat.

  • StrictSoftFileLock creates <path>.filelock and its claims directory with the lock’s mode and sets the sticky bit (a directory flag that lets only a file’s owner remove it) on both. On the next acquisition it narrows a directory it owns that an earlier release created wider; it leaves a directory of another account as it is, so remove such leftovers by hand. It reaches claims through a descriptor opened without following symlinks; on Windows it holds both directories open and refuses a junction (a directory that points at another directory). Under a sticky parent such as /tmp it refuses a coordination directory of another account, as SoftReadWriteLock does for <path>.rw. One read grace now covers a whole claim scan, a failed claim cleanup no longer leaves the sentinel descriptor open, and a non-regular node at a sentinel private-record name raises SoftFileLockProtocolError. On Windows, contenders read claims without blocking their owner’s unlink. PR #839 - by @gaborbernat.

  • Fix a lock reporting a hold the OS had dropped after a signal handler raised during acquire() or release(). A release that a signal handler cuts short after it has started now completes before the error propagates. A handler that acquires the lock its own thread is releasing now gets RuntimeError instead of a hold that stays held. The singleton cache no longer hands out an is_singleton=True lock or a ReadWriteLock whose finalizer is running, which released the new holder’s lock. A signal during os.fork() no longer leaves the other threads blocked in their next lock call, and a fork from another thread no longer waits for a ReadWriteLock acquire on a busy database. PR #840 - by @gaborbernat.

  • Fix a lock on a relative path locking a file in the directory the process changed to while the lock waited, and leaving its marker behind on release after such a change. The path resolved when an acquisition starts stays in use until the hold ends. An is_singleton=True lookup no longer returns a cached lock whose relative path now resolves to another file; a held lock still matches the file it holds. A rename of the lock’s directory during a hold no longer leaves the marker behind or reports a false lease compromise, and a waiter no longer creates a second lock file in a directory that was renamed away. PR #841 - by @gaborbernat.

  • SoftReadWriteLock reaches its records through descriptors of <path>.rw and gen or holders opened without following symlinks. On Windows it holds those directories open and refuses a junction (a directory that points at another directory). A tree swapped in after an earlier acquire therefore cannot redirect them. Its heartbeat rewrites a holder record only when the name still leads to the file it created, and does not block on a FIFO (a named pipe). A heartbeat that finds <path>.rw replaced by a symlink or junction reports the hold lost instead of ending the heartbeat thread. A live lock object repeats the ownership check whenever the tree it opens is not the one it prepared. PR #842 - by @gaborbernat.

  • Fix SoftFileLock, MarkerSoftFileLock and SoftFileLease removing or displacing a live holder’s marker in five cases. ext4 gave a freed inode number to the next marker. A stalled FileLock removed its ENOSYS placeholder after a peer replaced it. A failed acquire cleaned up after a successor took the path. A stale break left the lock path empty while it checked the marker it had moved. A lifetime break judged a marker by a reused inode number. On Linux and macOS a stale marker now changes hands in one atomic exchange. On other platforms without one, except Windows, including NFS and SMB, the marker stays in place and waits for timeout or break_lock(). Restoring a marker no longer follows a symlink found under the break name. A holder that releases while a peer’s exchange has its marker aside leaves a record no lock parses, so the put-back lands a marker that ages out. A lease heartbeat that finds another file at the lock path once, while a peer’s break has its marker aside, waits for the next refresh before it reports a compromise. A contender that finds a dead breaker’s marker puts back the marker that breaker left under its break name. A marker is the breaker’s own only when it names the breaker’s process on this host; the same process number on another host is a holder. A contender takes a stranded marker under a name of its own before it puts the marker back, so two contenders no longer reverse each other’s exchange and remove the live holder’s marker. A claim left behind by a failed put-back is retried on the next poll. PR #843 - by @gaborbernat.

  • Canceling an AsyncReadWriteLock or AsyncSoftReadWriteLock acquire that waits for another process now stops the wait and raises the cancellation. It used to wait until that process released, or raise Timeout. A thread_local=True AsyncFileLock that threads with their own event loops share no longer deadlocks when one thread waits for the lock while another holds it. PR #844 - by @gaborbernat.

  • SoftReadWriteLock refuses a commit that lands a whole retention window behind the log, so two committers stalled at adjacent generations can no longer admit a second writer. Its stale check samples the clock before it reads a holder record, so a pause between the two no longer counts as time the record stayed unchanged. PR #845 - by @gaborbernat.

  • Fix a SoftFileLock contender evicting an empty marker after two seconds while its owner held the lock. The owner is a SoftFileLock before 3.22, which writes nothing into its marker, or a current one still writing its record. An empty marker now reads as held, and timeout, lifetime or break_lock() clears it. A contender that finds an empty marker a dead breaker left aside puts it back instead of removing it. PR #846 - by @gaborbernat.

  • Fix ReadWriteLock on Windows acting on -journal and -wal files another account places beside its database: an acquire could fail with sqlite3.OperationalError, or SQLite could replay the journal and delete the file it names. The lock now reads the database directory’s access control list. Where an account other than the locking account, SYSTEM and Administrators can add or remove names, SQLite opens a hard link inside a directory only those accounts can write. A filesystem without hard links raises OSError. PR #854 - by @gaborbernat.

  • Fix SoftReadWriteLock subclasses sharing singleton instances with the base class or sibling subclasses. Each concrete class now keeps its own cache and construction guard, including after fork, while preserving cooperative class-definition keywords. PR #855 - by @AryanRG.

4.2.0 (2026-10-14)

Fixed

Performance

4.1.2 (2026-10-14)

Fixed

  • Fix read-lock acquisition on SQLite versions older than 3.33.0 by using sqlite_master for the schema probe. PR #827 - by @DawnofGenX.

  • Raise IsADirectoryError (or PermissionError on Windows) for a directory lock path regardless of its write permissions. PR #822 - by @feiiiiii5.

  • Fix a forked child hanging at exit on PyPy when the parent forked while an AsyncReadWriteLock worker thread was between two operations. PR #828 - by @gaborbernat.

  • Hash all cache keys in the lock filename recipe so short path-like keys stay in the chosen directory. The example also accepts path-derived surrogate escapes and works before the cache directory exists. PR #824 - by @xiehuanyi.

4.1.1 (2026-10-11)

Fixed

  • Avoid the macOS /dev/fd lookup implicated in a kernel panic by opening SQLite through a validated hard link. The database directory must be writable and support hard links; alias creation failure raises OSError. PR #820 - by @gaborbernat.

  • Ignore non-ASCII PID or start-time fields and out-of-range PIDs in ReadWriteLock hard-link directory names so forged owners cannot abort acquisition. PR #821 - by @dxbjavid.

4.1.0 (2026-10-09)

Added

Fixed

Documentation

  • The documentation’s canonical link, og:url and sitemap entries point at the versioned page on Read the Docs instead of a domain-root URL that returns 404. PR #813 - by @gaborbernat.

4.0.12 (2026-10-05)

Fixed

  • Fix read-lock acquisition on SQLite versions older than 3.33.0 by using sqlite_master for the schema probe. PR #769 - by @Vikram12301.

4.0.11 (2026-10-05)

Fixed

  • ReadWriteLock and AsyncReadWriteLock use a validated private hard link when /dev/fd has no entry for the database descriptor, supporting NetBSD’s static descriptor directory beyond descriptor 63. The temporary location must share a filesystem with the database. The symlink refusal test accepts NetBSD’s error wording - by @gaborbernat.

4.0.10 (2026-10-03)

Fixed

  • Reusing a singleton AsyncFileLock with another loop, executor or run_in_executor, or a singleton SoftFileLease with another lease_duration, heartbeat_interval or on_compromise, now raises ValueError instead of returning the lock with its original options. PR #765 - by @kokotatan.

4.0.9 (2026-10-01)

Fixed

  • ReadWriteLock and AsyncReadWriteLock close the descriptor that checks the database path once SQLite has connected, so on PyPy a dropped lock leaves no descriptor open until garbage collection runs. PR #763 - by @gaborbernat.

Security

  • ReadWriteLock and AsyncReadWriteLock refuse a symlink at the database path instead of following it, so a user who can create names in a shared lock directory cannot point the lock at another file (GHSA-j8f7-rjxc-mr56) - by @gaborbernat.

4.0.8 (2026-10-01)

Fixed

  • ReadWriteLock.release() and SoftReadWriteLock.release() from a thread that does not hold the write lock now raise RuntimeError instead of dropping the holder’s lock and letting a second writer in. PR #761 - by @feiiiiii5.

4.0.7 (2026-09-29)

Fixed

  • File locks now raise ValueError at construction when mode denies the owner read or write, such as mode=0o444, instead of failing on a later acquire and staying broken until someone deletes the lock file. PR #760 - by @gaborbernat.

4.0.6 (2026-09-28)

Fixed

  • Reject negative blocking timeouts other than -1 before reentrant ReadWriteLock and SoftReadWriteLock acquisition. Preserve unlimited waits and nonblocking acquisition. PR #756 - by @feiiiiii5.

4.0.5 (2026-09-28)

Fixed

  • Fix MarkerSoftFileLock acquisition and prevent contenders from evicting live protocol-2 owners after two seconds. Reclaim recognized records after owner death; preserve unknown contracts. PR #751 - by @DawnofGenX.

  • Honor instance timeout and blocking settings in sync and async ReadWriteLock acquisition, including waits between tasks on one instance. Preserve explicit per-call overrides. PR #752 - by @DawnofGenX.

  • Skip access-denial checks when the process can read mode-0o000 files. Keep mode-bit checks enabled for privileged processes on filesystems that support POSIX permissions. PR #753 - by @pasmud.

  • Skip vanished StrictSoftFileLock claims after a read-permission retry expires. Recheck the directory before raising a protocol error so concurrent removal does not turn a stale claim listing into an acquisition failure. PR #754 - by @januththedev.

  • Reject negative timeouts other than -1 in blocking AsyncReadWriteLock and AsyncSoftReadWriteLock acquisitions. Keep -1 as an unlimited wait and ignore timeouts when blocking=False. PR #755 - by @pasmud.

4.0.4 (2026-09-26)

Fixed

  • Hostnames that still differ after their first 253 escaped characters now publish distinct owners, so a soft lock no longer takes another such host’s live holder for its own and reclaims its marker. PR #748 - by @feiiiiii5.

4.0.3 (2026-09-23)

Fixed

  • Importing filelock on CPython 3.10 or 3.11 no longer makes new threads fail with RuntimeError: Cannot install a trace function while another trace function is being installed under coverage or a debugger. filelock skips its fork-safety audit hook there, so forking from inside a thread’s own lock-state transition no longer raises immediately on those versions. PR #747 - by @gaborbernat.

4.0.2 (2026-09-23)

Fixed

  • Concurrent acquire() and release() on a thread_local=False lock no longer leak the OS lock, close a descriptor twice, drop a lease token, or leave a false deadlock after a cross-thread release (issue #744). PR #745 - by @gaborbernat.

  • AsyncReadWriteLock and AsyncSoftReadWriteLock now give each asyncio task its own hold, so tasks sharing one instance no longer enter the write lock together. PR #746 - by @gaborbernat.

  • Correct the async cache example to create its data directory and clarify automatic creation of lock-file parent directories. PR #740 - by @agammann.

4.0.1 (2026-09-19)

Fixed

  • poll_interval is now validated at construction, on the setter, and on acquire(): a negative, non-finite, or non-numeric value raises ValueError/TypeError immediately instead of failing inside time.sleep. PR #739 - by @Pitchfork-and-Torch.

4.0.0 (2026-09-17)

Added

Fixed

  • The SoftReadWriteLock on-disk protocol is a generation log under <path>.rw, and a process running an earlier release does not see it: an old and a new participant on one lock path do not exclude each other. Stop every participant, upgrade them all, then restart them; the new code ignores leftover .state, .write and .readers/ files, and you can delete them. The filesystem must provide no-replace hard links, as it must for StrictSoftFileLock, so a runtime without os.link raises SoftFileLockProtocolError on acquire. Constructing a singleton again with a different on_compromise, or with poll_interval at or above stale_threshold, now raises ValueError. PR #738 - by @gaborbernat.

  • SoftReadWriteLock no longer deadlocks when a holder dies on another host mid-transition, and release() no longer waits on a mutex a dead host left behind. The state mutex is gone. Each transition is one atomic snapshot commit, and liveness is a heartbeat nonce read on the observer’s own clock rather than an mtime read against another host’s. PR #738 - by @gaborbernat.

3.32.7 (2026-09-16)

Fixed

  • Document that acquire() reads blocking=None as the lock’s blocking attribute and raises Timeout after one attempt when blocking=False. PR #733 - by @hxperl.

3.32.6 (2026-09-08)

Added

  • Allow acquiring existing lock files that grant write access through group permissions or an ACL even when their owner-write mode bit is unset. PR #728 - by @jackwalkerlabs.

Fixed

  • SoftFileLease and AsyncSoftFileLease now reject a boolean or non-finite lease_duration, which used to publish an owner record their own owner property reads back as malformed. PR #723 - by @lprnmns.

  • Reject non-finite heartbeat, stale, and polling intervals in SoftReadWriteLock and AsyncSoftReadWriteLock, including cached singleton construction and overflow in the default stale threshold. PR #724 - by @lprnmns.

  • Honor acquisition timeouts and blocking=False during SoftReadWriteLock state-mutex contention, including failed writer cleanup. Cross-host recovery of an abandoned .state marker remains unsupported. PR #726 - by @Sohel2309.

  • Preserve exception notes and custom attributes when copying or pickling Timeout and SoftFileLockProtocolError. PR #729 - by @jackwalkerlabs.

3.32.5 (2026-08-31)

Fixed

  • SoftFileLease.token and AsyncSoftFileLease.token now read None after a failed acquisition, so a contender turned away by a live holder no longer reports a token for a claim it never published. PR #721 - by @lprnmns.

  • Document that mode has no setter: unlike poll_interval, timeout, blocking and lifetime, it is fixed at construction and lock.mode = ... raises AttributeError. PR #716 - by @Gares95.

3.32.4 (2026-08-23)

Fixed

  • StrictSoftFileLock always retries a claim read whose first attempt reports the claim as pending, so a first read that itself outlasts the retry grace no longer fails closed on a claim it could have read. PR #705 - by @gaborbernat.

  • WindowsFileLock waits out a transient STATUS_ACCESS_DENIED from NtCreateFile for up to half a second before raising PermissionError, since a peer unlinking the lock file as it releases can answer that for a moment; a real denial still fails fast. PR #705 - by @gaborbernat.

  • Every lock class now escapes the hostname it publishes, so a host whose socket.gethostname() carries a space, a newline or a byte outside UTF-8 no longer writes a marker it reads back as malformed. Such a host used to lose a held SoftReadWriteLock read slot to a peer and could not take a write slot or a StrictSoftFileLock at all. PR #709 - by @dxbjavid.

3.32.3 (2026-08-13)

Fixed

  • The fork-safety audit hook no longer prints Exception ignored in audit hook with a TypeError when an audit event fires during interpreter shutdown, after CPython has already cleared the module globals. PR #703 - by @gaborbernat.

3.32.2 (2026-07-29)

Fixed

  • A SoftReadWriteLock or SoftFileLease acquire whose heartbeat thread fails to start now unlinks its marker and hands the claim back, instead of leaving an unrefreshed marker a peer takes while the caller believes it still holds the lock. PR #691 - by @dxbjavid.

  • Canceling an AsyncSoftReadWriteLock acquire now releases the claim instead of leaking a marker whose heartbeat wedges every contender. PR #686 - by @dxbjavid.

3.32.0 (2026-07-21)

Added

  • Declare support for Python 3.15 and run the test suite against it and its free-threaded build, both currently in beta. PR #683 - by @gaborbernat.

Fixed

  • SoftReadWriteLock closes the directory handle it opens to scan for readers as soon as a scan stops early, rather than holding it until the generator is collected. PR #685 - by @gaborbernat.

3.31.2 (2026-07-21)

Fixed

  • filelock imports again on runtimes whose errno omits ENOTSUP, such as GraalPy, where importing the package raised ImportError. It probes the code instead, preferring ENOTSUP, falling back to EOPNOTSUPP where that name is absent, and dropping to ENOSYS/EXDEV where neither exists. Platforms defining ENOTSUP keep their behavior. PR #681 - by @gaborbernat.

3.31.1 (2026-07-20)

Fixed

  • A SoftFileLease acquired on one thread keeps its claim when another thread fails to acquire the same lease object, so its heartbeat carries on refreshing the marker instead of being torn down and letting a peer take the live claim. PR #680 - by @dxbjavid.

3.31.0 (2026-07-18)

Added

  • Support Termux/Android, whose CPython ships without os.link and reports sys.platform == "android". import filelock and both FileLock and SoftFileLock now work there, StrictSoftFileLock reports its missing hard-link support only when acquired, and process liveness reads /proc on Android instead of PID-only checks. PR #678 - by @gaborbernat.

Fixed

  • StrictSoftFileLock no longer lets two processes hold the lock at once under heavy contention. A holder now keeps its intent claim for the whole hold, so a contender whose directory scan races the holder’s freshly linked claim can no longer miss it and win alongside it. A held lock now exposes both an intent and a held claim. PR #678 - by @gaborbernat.

3.30.3 (2026-07-17)

Fixed

  • AsyncFileLock and AsyncSoftFileLock no longer raise a deadlock RuntimeError when a different asyncio task waits for a lock they hold; the check now scopes holders to the owning task, so only a same-task reacquire is refused. PR #676 - by @xt-yin.

Documentation

  • Keep both tables of contents on screen at any browser font size. The widened body column sized itself in em against the reader’s font size while the breakpoints that fold the layout resolve em against a fixed 16px, so a reader whose default font is larger than 16px had the right-hand table of contents clipped off the edge. The body column now flexes instead, and the right-hand table of contents hides only at the mobile breakpoint. PR #673 - by @gaborbernat.

  • Color the mermaid diagrams from whichever theme is active. They previously carried a light palette baked into each diagram’s source, which stayed light on a dark page. PR #674 - by @gaborbernat.

3.30.2 (2026-07-16)

Fixed

  • Stop SoftFileLease deleting a live marker whose mode it does not implement. An unrecognized mode now names its owner instead of reading as malformed, so a record written by a newer filelock survives the grace window rather than being evicted after two seconds. PR #672 - by @gaborbernat.

  • Stop StrictSoftFileLock and AsyncStrictSoftFileLock calling themselves a native OS lock when they warn that they ignore lifetime; they now say a strict claim is only ever cleared by force_break(). PR #672 - by @gaborbernat.

Documentation

  • Cover every lock type in the tutorials and how-to guides, with examples drawn from projects that use filelock, and color the mermaid diagrams. Correct the claims that StrictSoftFileLock exposes owner, that SoftFileLock evicts a strict sentinel, that ReadWriteLock requires a .db extension, and that every log record is DEBUG. PR #672 - by @gaborbernat.

3.30.1 (2026-07-16)

Fixed

  • StrictSoftFileLock and AsyncStrictSoftFileLock no longer abort acquisition when a peer’s claim vanishes as an NFSv3 stale filehandle instead of a clean removal; the reader revalidates and skips it, so strict locks hold mutual exclusion across independent NFSv3 client caches. PR #669 - by @gaborbernat.

  • SoftFileLease reads a marker whose lease duration is nan or inf as malformed rather than as a valid lease, so such a marker ages out through the grace window instead of raising LeaseSettingsMismatch on every contender. PR #670 - by @dxbjavid.

3.30.0 (2026-07-16)

Added

  • Add context_error_policy to surface body and release failures as a BaseExceptionGroup. PR #618 - by @gaborbernat.

  • Add close_error_policy to control an os.close() failure after the OS unlock. PR #619 - by @gaborbernat.

  • Add lock_descriptor() and unlock_descriptor() to lock a caller-owned file descriptor. PR #620 - by @gaborbernat.

  • Add fallback_to_soft to fail closed instead of downgrading to SoftFileLock on ENOSYS. PR #622 - by @gaborbernat.

  • Add preserve_lock_file so native locks keep the lock pathname on release. PR #624 - by @gaborbernat.

  • Add an on_acquired hook that runs a callback on the locked descriptor once the lock is held. PR #625 - by @gaborbernat.

  • Add StrictSoftFileLock, which treats every marker it did not publish as contention, and SoftFileLease, whose claim expires and whose holder learns through on_compromise when it is lost. SoftFileLock(lifetime=...) now warns and names them. Both publish a record SoftFileLock evicts, so do not mix contracts on one path. PR #658 - by @gaborbernat.

  • Add StrictSoftFileLock and AsyncStrictSoftFileLock with owner-specific hard-linked claims. Strict mode fails closed on claim damage, exposes each recovery token, and requires an exact claim name for a force break. PR #659 - by @gaborbernat.

Changes

  • Make native lock release transactional so a failed unlock is retried, not dropped. PR #615 - by @gaborbernat.

Fixed

  • Ignore lifetime on native locks with a warning; only SoftFileLock honors it. PR #593 - by @gaborbernat.

  • Stop mutating the Unix lock file before fcntl.flock() is held. PR #594 - by @gaborbernat.

  • Evict a non-regular soft lock file without reading it. PR #597 - by @dxbjavid.

  • Write a soft lock’s holder record atomically so a short write cannot create overlapping holders. PR #614 - by @gaborbernat.

  • Open the Windows lock file via NtCreateFile so a real access denial is raised, not reported as a timeout. PR #617 - by @gaborbernat.

  • Canonicalize singleton keys so equivalent path spellings share an instance, without following a final symlink. PR #621 - by @gaborbernat.

  • Preserve parent lock ownership across fork(). A child closes an inherited descriptor only while its identity still matches, so it neither unlocks nor unlinks the parent’s lock. It clears inherited ownership state and singleton caches, then requires a new lock instance before acquiring. Fork-time construction replaces inherited cache mutexes and omits an instance whose construction crossed into the child from the cache. If fstat() cannot establish the identity, child cleanup skips the descriptor number because an earlier fork callback may have reused it. Descriptor tracking also covers third-party backends that fail after assigning a descriptor. PR #652 - by @gaborbernat.

  • Open a SQLite connection per outer acquisition and close it on final release, so a forked child can no longer close its parent’s handle. A child rejects inherited databases until exec(), and a PyPy child rejects read-write locks once the parent has used SQLite. PR #657 - by @gaborbernat.

  • Keep executor-backed lock operations alive until they finish when a caller cancels. Serialize acquisitions on the same async lock so cancellation rollback cannot release a later caller’s hold. Roll back acquisitions that finish after cancellation and surface release failures. SQLite read-write locks keep their state until rollback ends the transaction; callers can retry cleanup with release(force=True) or another acquisition. PR #652 - by @gaborbernat.

  • Reject negative and non-finite lifetime values during lock construction and assignment. PR #644 - by @gaborbernat.

  • Read the saved Windows process-probe error before deciding that a soft-lock holder has exited. PR #645 - by @gaborbernat.

  • Reject non-default lock options that a narrow subclass constructor cannot honor, and forward all options through subclasses that accept arbitrary keyword arguments. PR #646 - by @gaborbernat.

  • Release now removes the path identity that acquisition recorded instead of resolving a symlinked parent again. When acquisition raises, its rollback no longer deletes the holder’s deadlock record. PR #647 - by @gaborbernat.

  • Remove the body error from implicit context on the release error before adding both errors to an exception group. PR #648 - by @gaborbernat.

  • Relinquish a soft lock’s descriptor before one close attempt so a later release cannot close a reused descriptor number, and honor close_error_policy while still attempting marker cleanup. PR #649 - by @gaborbernat.

  • Raise OSError(errno.ENOSYS) from descriptor locks when fcntl is unavailable, and reject invalid blocking poll intervals before attempting a lock. PR #650 - by @gaborbernat.

  • SoftFileLock and SoftFileLease record the holder’s process start time on every platform and reclaim a stale marker only when the recorded owner is provably gone. A reused PID on Unix no longer breaks a live lock or wedges acquisition, matching the recycled-PID detection Windows already had. PR #660 - by @gaborbernat.

  • SoftReadWriteLock and SoftFileLease ride out a transient filesystem error during a heartbeat instead of dropping the lock or reporting a false compromise, and report a lost claim only once refresh cannot recover before the lease would lapse. SoftFileLease also reclaims a malformed or partial marker that used to block every contender, and a Linux marker distinguishes a PID reused across a reboot. PR #661 - by @gaborbernat.

  • BaseFileLock.__del__ suppresses a release error rather than raising it during garbage collection, where it surfaced as an unraisable-exception warning attributed to unrelated code. PR #665 - by @gaborbernat.

Documentation

  • Correct the Unix lock-file cleanup and fcntl.flock() documentation. PR #623 - by @gaborbernat.

  • Refresh the documentation to the current code: a new trust-boundaries and ownership-scope section (same-UID boundary, advisory native locks, strict claims and lease fencing, a filesystem support matrix, migration off timed stale breaking), the lock-selection flowchart and comparison tables extended with StrictSoftFileLock and SoftFileLease, and the cross-platform process start token replacing the Windows-only description throughout. PR #663 - by @gaborbernat.

  • The filesystem support matrix records mutual exclusion measured across two independent client caches in CI: NFS (v4 and v3) for the native, soft, and strict locks, and SMB for the native and soft locks. PR #665 - by @gaborbernat.

Security

  • Bind the Windows reparse-point check to the locked handle to close a symlink-swap race. PR #596 - by @gaborbernat.

3.29.7 (2026-07-07)

Fixed

3.29.6 (2026-07-06)

Fixed

3.29.5 (2026-07-02)

Changes

Fixed

3.29.4 (2026-06-13)

Fixed

  • Keep the read/write heartbeat alive on a transient touch error PR #562 - by @dxbjavid

  • Verify inode in break_lock_file before unlinking a stale lock PR #561 - by @dxbjavid

3.29.3 (2026-06-10)

Fixed

3.29.2 (2026-06-10)

Fixed

Security

  • Open marker reads non-blocking to refuse attacker-placed fifo PR #549 - by @dxbjavid

3.29.1 (2026-06-03)

Documentation

Security

3.29.0 (2026-04-19)

Added

Fixed

  • Use one executor thread for async lock operations to keep acquisition and release ordered. PR #533 - by @gaborbernat.

3.28.0 (2026-04-14)

Added

Fixed

3.25.2 (2026-03-11)

Fixed

3.25.1 (2026-03-09)

Fixed

Documentation

3.25.0 (2026-03-01)

Added

Fixed

3.24.3 (2026-02-19)

Fixed

3.24.2 (2026-02-16)

Fixed

Documentation

  • Organize the documentation into tutorials, how-to guides, reference, and explanations. PR #489 - by @gaborbernat.

3.24.1 (2026-02-15)

Fixed

3.24.0 (2026-02-14)

Added

Fixed

3.23.0 (2026-02-14)

Changes

Documentation

3.22.0 (2026-02-14)

Added

Fixed

  • Disable stale-lock detection on Windows until its implementation is safe. PR #477 - by @gaborbernat.

3.21.2 (2026-02-13)

Fixed

3.21.1 (2026-02-12)

Fixed

3.21.0 (2026-02-12)

v3.20.4 (2026-03-23)

Added

v3.20.3 (2026-01-09)

Security

v3.20.2 (2026-01-02)

Added

v3.20.1 (2025-12-15)

Security

v3.20.0 (2025-10-08)

Added

v3.19.1 (2025-08-14)

Added

v3.18.0 (2025-03-11)

Added

Fixed

v3.17.0 (2025-01-21)

Added

v3.16.1 (2024-09-17)

v3.16.0 (2024-09-07)

Added

v3.15.4 (2024-06-22)

Fixed

v3.15.3 (2024-06-19)

Fixed

  • Fix TypeError: _CountedFileLock.__init__() got an unexpected keyword argument PR #345 - by @kwist-sgr.

v3.15.2 (2024-06-19)

Changes

v3.15.1 (2024-06-12)

Fixed

  • Restore __init__ method; more robust initialization for singleton locks PR #338 - by @ethanbb.

v3.15.0 (2024-06-11)

Added

Fixed

v3.14.0 (2024-04-27)

Added

v3.13.4 (2024-04-09)

Fixed

  • Raise error on incompatible singleton timeout and mode arguments PR #320 - by @nefrob.

v3.13.3 (2024-03-25)

Changes

  • Make singleton class instance dict unique per subclass PR #318 - by @nefrob.

v3.13.2 (2024-03-25)

Fixed

v3.13.1 (2023-10-30)

Added

v3.13.0 (2023-10-27)

Added

  • Support reentrant locking on lock file path via optional is_singleton instance PR #283 - by @nefrob.

v3.12.4 (2023-09-13)

Packaging

  • Change typing-extensions to be installed only with the [typing] extra PR #276 - by @asottile.

v3.12.3 (2023-08-28)

Added

v3.12.2 (2023-06-12)

Fixed

v3.12.1 (2023-06-10)

Added

v3.12.0 (2023-04-18)

Changes

  • Allow callers to disable thread-local lock state at construction; it remains enabled by default. PR #232 - by @csm10495.

Fixed

v3.11.0 (2023-04-06)

Changes

v3.10.7 (2023-03-27)

Fixed

v3.10.6 (2023-03-25)

Fixed

v3.10.5 (2023-03-25)

Added

v3.10.4 (2023-03-24)

Fixed

  • Apply the requested file mode when creating a Unix lock file, including when the file cannot later be changed with chmod. PR #211 - by @jahrules.

v3.10.3 (2023-03-23)

Fixed

v3.10.2 (2023-03-22)

Fixed

  • Bug fix for using filelock with threaded programs causing undesired file permissions - by @jahrules.

v3.10.1 (2023-03-22)

Fixed

v3.10.0 (2023-03-15)

Added

v3.9.1 (2023-03-14)

Changes

v3.9.0 (2022-12-28)

v3.8.2 (2022-12-05)

Packaging

  • Update the published license classifier to Unlicense. PR #180 - by @jond01

v3.8.1 (2022-12-04)

Fixed

v3.8.0 (2022-12-04)

Added

Documentation

v3.7.1 (2022-05-31)

Documentation

  • Point README documentation links to the site index - by @gaborbernat.

v3.7.0 (2022-05-13)

Added

  • Allow a lock attempt to return immediately when the lock cannot be acquired. PR #142 - by @myheroyuki.

v3.6.0 (2022-02-17)

Fixed

v3.5.1 (2022-02-16)

Fixed

  • Use a monotonic clock for timeouts so wall-clock changes do not change their duration. PR #136 - by @avian2.

v3.5.0 (2022-02-15)

Added

  • Allow a lock to be used as a context decorator. PR #128 - by Hirsch Alter.

v3.4.2 (2021-12-16)

Added

v3.4.1 (2021-12-16)

Fixed

  • Point argument deprecation warnings at the caller by setting stacklevel. PR #121 - by @XuehaiPan.

v3.4.0 (2021-11-16)

Deprecated

  • Add correct spelling of poll interval parameter for acquire() method, raise deprecation warning when using the misspelled form PR #119 - by @XuehaiPan.

v3.3.2 (2021-10-29)

Added

v3.3.1 (2021-10-15)

Fixed

  • Leave the log level of the filelock logger as not set (previously was set to warning) PR #108 - by @gaborbernat

Documentation

v3.3.0 (2021-10-03)

Added

Documentation

v3.2.1 (2021-10-02)

Fixed

  • Changed logger name from filelock._api to filelock PR #97 - by @hkennyv

Documentation

  • Add a tutorial covering lock acquisition, timeouts, and recursive use - by @gaborbernat.

v3.2.0 (2021-09-30)

Changes

Fixed

v3.1.0 (2021-09-27)

Documentation

v3.0.12 (2019-05-18)

Fixed

  • Fix package setup on Python 2 - by Benedikt Schmitt.

Documentation

  • Correct the timeout documentation: zero means no wait, while a negative value waits indefinitely. issue #49 - by Benedikt Schmitt; reported by @AnkurTank.

Packaging

  • Fix package upload to PyPI by keeping the license summary on one line - by Benedikt Schmitt.

  • Update the published PyPI classifiers - by @hugovk.

v3.0.10 (2018-11-01)

Documentation

v3.0.9 (2018-10-02)

v3.0.8 (2018-09-09)

Fixed

  • Fix a memory leak when acquiring locks on Python 2. issue #37 - by Benedikt Schmitt.

v3.0.6 (2018-08-22)

v3.0.4 (2018-02-01)

v3.0.3 (2018-01-30)

Documentation

  • Render the animated README example through a raw image URL on PyPI - by Benedikt Schmitt.

v3.0.2 (2018-01-30)

Fixed

  • Keep the Unix lock file after release to avoid a race between processes. issue #31 - by Benedikt Schmitt.

Documentation

  • Add navigation to the README - by Benedikt Schmitt.

  • Clarify when to use a separate lock file in the documentation. issue #22 - by Benedikt Schmitt.

v3.0.0 (2018-01-05)

Changes

Fixed

  • Reraise lock errors correctly on Python 3 - by Benedikt Schmitt.

v2.0.13 (2017-11-05)

Fixed

  • Acquire the logger only when first needed. issue #24 - by Benedikt Schmitt.

v2.0.12 (2017-09-02)

v2.0.11 (2017-07-19)

Added

  • Add official Python 2 support. issue #20 - by Benedikt Schmitt.

v2.0.10 (2017-06-07)

v2.0.9 (2017-06-07)

Fixed

Documentation

  • Explain why the protected file needs a separate lock path. issue #19 - by Benedikt Schmitt.

  • Correct the Read the Docs URL - by Benedikt Schmitt.

v2.0.8 (2017-01-24)

Added

  • Add logging for lock operations - by @expobrain.

v2.0.7 (2016-11-05)

Packaging

  • Keep the README and license out of the installed environment root while including them in the source archive. issue #14 - by Benedikt Schmitt.

v2.0.6 (2016-05-01)

Fixed

  • Clear the lock descriptor before unlocking on Unix and Windows, avoiding a repeated release. - by @cottsay.

v2.0.5 (2015-11-11)

Fixed

  • Keep the lock file after release - by @kramarz.

Added

  • Export BaseFileLock as a public class - by Benedikt Schmitt.

Documentation

  • Show the complete public API in the reference page - by Benedikt Schmitt.

v2.0.4 (2015-07-29)

Added

  • Export the platform-specific lock classes consistently through __all__ - by Benedikt Schmitt.

v2.0.1 (2015-06-13)

Fixed

  • Treat Windows lock contention as a timeout instead of raising IOError. issue #5 - by Benedikt Schmitt.

  • Avoid an extra acquisition for with lock.acquire(timeout=...), so one release frees the lock. issue #3 - by Benedikt Schmitt.

Documentation

  • Add a documentation site with recursive locking examples and an API reference - by Benedikt Schmitt.

v2.0.0 (2015-05-25)

Added

  • Allow a default timeout at lock construction. issue #2 - by Benedikt Schmitt.

v1.0.3 (2015-04-22)

Fixed

  • Handle an exception while acquiring a lock - by Benedikt Schmitt.

v1.0.2 (2015-04-22)

Fixed

  • Do not time out when the same lock object already holds the lock - by Benedikt Schmitt.

v1.0.1 (2015-04-22)

Fixed

  • Return immediately after a successful first acquire instead of waiting one poll interval. issue #1 - by Benedikt Schmitt.

v1.0.0 (2015-04-07)

Added

  • Add a lock counter so the same lock object can acquire recursively - by Benedikt Schmitt.

v0.2.2 (2015-03-25)

Packaging

  • Include the renamed README and license files in the source distribution - by Benedikt Schmitt.

v0.2.1 (2015-03-25)

Packaging

  • Publish the README and license as reStructuredText so package metadata can render them - by Benedikt Schmitt.

v0.2.0 (2014-07-06)

Added

  • Add file locks for Unix and Windows - by Benedikt Schmitt.