API Reference

API reference for flufl.lock:

class flufl.lock.Lock(lockfile, lifetime=None, separator='|')

Portable, NFS-safe file locking with timeouts for POSIX systems.

This class implements an NFS-safe file-based locking algorithm influenced by the GNU/Linux open(2) manpage, under the description of the O_EXCL option:

[…] O_EXCL is broken on NFS file systems, programs which rely on it for performing locking tasks will contain a race condition. The solution for performing atomic file locking using a lockfile is to create a unique file on the same fs (e.g., incorporating hostname and pid), use link(2) to make a link to the lockfile. If link() returns 0, the lock is successful. Otherwise, use stat(2) on the unique file to check if its link count has increased to 2, in which case the lock is also successful.

The assumption made here is that there will be no outside interference, e.g. no agent external to this code will ever link() to the specific lock files used.

The user specifies a lock file in the constructor of this class. This is whatever file system path the user wants to coordinate locks on. When a process attempts to acquire a lock, it first writes a claim file which contains unique details about the lock being acquired (e.g. the lock file name, the hostname, the pid of the process, and a random integer). Then it attempts to create a hard link from the claim file to the lock file. If no other process has the lock, this hard link succeeds and the process accquires the lock. If another process already has the lock, the hard link will fail and the lock will not be acquired. What happens in this and other error conditions are described in the more detailed documentation.

Lock objects support lock-breaking so that you can’t wedge a process forever. This is especially helpful in a web environment, but may not be appropriate for all applications.

Locks have a lifetime, which is the maximum length of time the process expects to retain the lock. It is important to pick a good number here because other processes will not break an existing lock until the expected lifetime has expired. Too long and other processes will hang; too short and you’ll end up trampling on existing process locks – and possibly corrupting data. However locks also support extending a lock’s lifetime. In a distributed (NFS) environment, you also need to make sure that your clocks are properly synchronized.

Each process laying claim to this resource lock will create their own temporary lock file based on the path specified. An optional lifetime is the length of time that the process expects to hold the lock.

Parameters
  • lockfile (str) – The full path to the lock file.

  • lifetime (Union[timedelta, int, None]) – The expected maximum lifetime of the lock, as a timedelta or integer number of seconds, relative to now. Defaults to 15 seconds.

  • separator (str) – The separator character to use in the lock file’s file name. Defaults to the vertical bar (|) on POSIX systems and caret (^) on Windows.

property details: Tuple[str, int, str]

Details as read from the lock file.

Return type

Tuple[str, int, str]

Returns

A 3-tuple of hostname, process id, lock file name.

Raises

NotLockedError – if the lock is not acquired.

property expiration: datetime.datetime

The lock’s expiration time, regardless of ownership.

Return type

datetime

property hostname: str

The current machine’s host name.

Return type

str

Returns

The current machine’s hostname, as used in the .details property.

property is_locked: bool

True if we own the lock, False if we do not.

Checking the status of the lock resets the lock’s lifetime, which helps avoid race conditions during the lock status test.

Return type

bool

property lifetime: datetime.timedelta

The current lock life time.

Return type

timedelta

lock(timeout=None)

Acquire the lock.

This blocks until the lock is acquired unless optional timeout is not None, in which case a TimeOutError is raised when the timeout expires without lock acquisition.

Parameters

timeout (Union[timedelta, int, None]) – Approximately how long the lock acquisition attempt should be made. None (the default) means keep trying forever.

Raises
  • AlreadyLockedError – if the lock is already acquired.

  • TimeOutError – if timeout is not None and the indicated time interval expires without a lock acquisition.

Return type

None

property lockfile: str

Return the lock file name.

Return type

str

refresh(lifetime=None, *, unconditionally=False)

Refreshes the lifetime of a locked file.

Use this if you realize that you need to keep a resource locked longer than you thought.

Parameters
  • lifetime (Union[timedelta, int, None]) – If given, this sets the lock’s new lifetime. It represents the number of seconds into the future that the lock’s lifetime will expire, relative to now, or whenever it is refreshed, either explicitly or implicitly. If not given, the original lifetime interval is used.

  • unconditionally (bool) – When False (the default), a NotLockedError is raised if an unlocked lock is refreshed.

Raises

NotLockedError – if the lock is not set, unless optional unconditionally flag is set to True.

Return type

None

property retry_errnos: List[int]

The set of errno values that cause a read retry.

Return type

List[int]

property state: flufl.lock._lockfile.LockState

Infer the state of the lock.

Return type

LockState

unlock(*, unconditionally=False)

Release the lock.

Parameters

unconditionally (bool) – When False (the default), a NotLockedError is raised if this is called on an unlocked lock.

Raises

NotLockedError – if we don’t own the lock, either because of unbalanced unlock calls, or because the lock was stolen out from under us, unless optional unconditionally is True.

Return type

None

class flufl.lock.LockState(value)

Infer the state of the lock.

There are cases in which it is impossible to infer the state of the lock, due to the distributed nature of the locking mechanism and environment. However it is possible to provide some insights into the state of the lock. Note that the policy on what to do with this information is left entirely to the user of the library.

unlocked = 1

There is no lock file so the lock is unlocked.

ours = 2

We own the lock and it is fresh.

ours_expired = 3

We own the lock but it has expired. Another process trying to acquire the lock will break it.

stale = 4

We don’t own the lock; the hostname in the details matches our hostname and there is no pid running that matches pid. Therefore, the lock is stale.

theirs_expired = 5

Some other process owns the lock (probably) but it has expired. Another process trying to acquire the lock will break it.

unknown = 6

We don’t own the lock; either our hostname does not match the details, or there is a process (that’s not us) with a matching pid. The state of the lock is therefore unknown.

Exceptions

exception flufl.lock.LockError

Base class for all exceptions in this module.

exception flufl.lock.AlreadyLockedError

An attempt is made to lock an already locked object.

exception flufl.lock.NotLockedError

An attempt is made to unlock an object that isn’t locked.

exception flufl.lock.TimeOutError

The timeout interval elapsed before the lock succeeded.