Troubleshooting¶
Six symptoms that show up more than once, each with its cause and its fix. This page assumes you already picked a lock class and are past the happy path; for that groundwork see Quickstart and Lock Types.
My lock doesn’t stop another process from writing¶
Cause: portalocker locks are advisory on POSIX: a process that
never asks for the lock is never blocked by it, and never learns one
exists. See Platform Behaviour for the full explanation, including a
demonstration of the clobber and why the same code is enforced on
Windows.
Fix: there is no flag that makes POSIX locking mandatory. Every process that touches the file, including code you do not control, has to take the same lock before reading or writing it.
The other process doesn’t see my data¶
Cause: a lock serialises access, not visibility – your bytes can still be sitting in a buffer when another process looks. See “Flush before you release” in Platform Behaviour for the two layers involved.
Fix: call flush(), then os.fsync, before another process needs
to see the write:
>>> import os
>>> import portalocker
>>> with portalocker.Lock('output.txt', 'w', timeout=5) as fh:
... _ = fh.write('visible to the next reader')
... fh.flush()
... os.fsync(fh.fileno())
AlreadyLocked raised immediately without waiting¶
Cause: two independent settings skip the retry loop and fail on the
very first contended attempt instead of waiting up to timeout:
fail_when_locked=True. TemporaryFileLock and PidFileLock both default to this, unlike Lock, so switching to either one changes this behaviour even iftimeoutitself is left untouched – see Lock Types.timeout=0. The retry clock in LockBase._timeout_generator only starts after the first attempt, so atimeoutof0still buys exactly one attempt and nothing more, regardless offail_when_locked.
>>> import portalocker
>>> first = portalocker.Lock('contended.lock', fail_when_locked=True)
>>> _ = first.acquire()
>>> second = portalocker.Lock('contended.lock', fail_when_locked=True)
>>> try:
... second.acquire()
... except portalocker.AlreadyLocked:
... print('raised on the first attempt, no retry')
raised on the first attempt, no retry
>>> first.release()
Fix: if retrying for the full timeout is what you want, leave
fail_when_locked at its default (False) and pass a positive
timeout.
A stale lock file is left behind after a crash¶
Cause: TemporaryFileLock and PidFileLock remove their lock file
in release, with two fallbacks for code that forgets to call it:
garbage collection (LockBase.__del__) and an atexit handler
registered when the lock is constructed. None of those three run when
the process is killed outright – SIGKILL, a segfault, a lost VM –
so the file stays on disk.
This is expected, and mostly harmless. The kernel releases the process’s advisory lock the moment it dies, file present or not, so the next acquire on the same path succeeds immediately; the leftover file is inert litter, not a lock that needs clearing by hand. For PidFileLock specifically, PidFileLock.read_pid can then report a PID that no longer exists – it only ever says who last wrote the file, never whether that process is still alive.
4.0.0 fixed a related but distinct race in the same area, tracked as
issue #115: a competing acquirer could open and lock the very inode a
releaser was in the middle of unlinking, so both processes believed they
held the lock at once (split-brain). acquire now re-verifies, after
locking, that its handle still names the current path, and retries
within the timeout if a race is detected.
Fix: nothing to do for the stale file itself; it is safe to ignore or delete. See Lock Types for what TemporaryFileLock guarantees on release, and PidFileLock.release for the PID file’s own release behaviour.
str() of a lock exception changed after upgrading¶
Cause: before 4.0.0, str(exc) on POSIX was whatever the bare
OSError from fcntl reported. Since 4.0.0, every lock failure –
POSIX and Windows alike – passes an error code as the first argument
and the message as the second, matching the two-argument convention
BaseLockException already followed on Windows. That makes
str(exc) on POSIX a 2-tuple repr instead of the bare message:
>>> from portalocker import exceptions
>>> exc = exceptions.LockException(
... exceptions.LockException.LOCK_FAILED,
... 'Resource temporarily unavailable',
... )
>>> str(exc)
"(1, 'Resource temporarily unavailable')"
>>> exc.strerror
'Resource temporarily unavailable'
Fix: read BaseLockException.strerror instead of parsing
str(exc); it has held the message consistently on both platforms
since 4.0.0. See Migrating from 3.x to 4.0.0 for the rest of what changed in that
release.