API reference

The FileLock object

class FileLock(lock_file='openlock.lock', timeout=None)

The lock constructor. An openlock.FileLock object supports the context manager protocol.

Parameters:
  • lock_file (str | Path) – the underlying file used for locking; the calling process should have read/write access

  • timeout (float | None) – the default for the corresponding argument of openlock.acquire()

acquire(timeout=None)

Attempts to acquires the lock.

Parameters:

timeout (float | None) – specifies the maximum waiting time in seconds before a Timeout exception is raised

Raises:
  • Timeout – raised when the waiting time for acquiring the lock has expired

  • InvalidLockFile – raised when openlock is unable to create a valid lock file

Return type:

None

getpid()

The PID of the process that holds the lock, if any. Otherwise returns None.

Return type:

int | None

lock_file: Path

The Path object representing the lock file.

locked()

True if we hold the lock.

Return type:

bool

release()

Releases the lock.

Raises:

InvalidRelease – raised when we don’t own the lock

Return type:

None

timeout: float | None

The value of the timeout parameter.

Exceptions

exception OpenLockException

Base exception raised by the openlock library.

exception Timeout

Bases: OpenLockException

Raised when the waiting time for acquiring a openlock.FileLock has expired.

exception InvalidLockFile

Bases: OpenLockException

Raised when openlock is unable to create a valid lock file in openlock.FileLock.acquire().

exception InvalidRelease

Bases: OpenLockException

Raised when openlock.FileLock.release() is called on a lock we do not own.

exception InvalidOption

Bases: OpenLockException

Raised when openlock.set_defaults() is used to set a non-existing option.

Options

class Defaults

Bases: TypedDict

Default options.

race_delay: float

delay before we check that we still have the lock file

retry_period: float

delay before reattempting to acquire a lock

tries: int

number of attempts to create a valid lock file

set_defaults(**kw)

Set defaults.

Parameters:

kw (Unpack[Defaults]) – default parameters

Raises:

InvalidOption – raised when trying to set a non-existing option

Return type:

None

get_defaults()

Get defaults.

Return type:

Defaults

Internals

How does it work

A valid lock file has two lines of text containing respectively:

  • pid: the PID of the process holding the lock;

  • name: the content of argv[0] of the process holding the lock.

A lock file is considered stale if the pair (pid, name) does not belong to a Python process in the process table.

A process that seeks to acquire a lock first atomically tries to create a new lock file. If this succeeds then it has acquired the lock. If it fails then this means that a lock file exists. If it is valid, i.e. not stale and syntactically valid, then this implies that the lock has already been acquired and the process will periodically retry to acquire it - subject to the timeout parameter. If the lock file is invalid, then the process atomically overwrites it with its own data. It sleeps race_delay seconds and then checks if the lock file has again been overwritten (necessarily by a different process). If not then it has acquired the lock.

Once the lock is acquired the process installs an exit handler to remove the lock file on exit.

To release the lock, the process deletes the lock file and uninstalls the exit handler.

It follows from this description that the algorithm is latency free in the common use case where there are no invalid lock files.

Issues

There are no known issues in the common use case where there are no invalid lock files. In general the following is true:

  • The algorithm for dealing with invalid lock files fails if a process needs more time than indicated by the race_delay parameter to create a new lock file after detecting the absence of a valid one. The library will issue a warning if it thinks the system is too slow for the algorithm to work correctly and it will recommend to increase the value of the race_delay parameter.

  • Since PIDs are only unique over the lifetime of a process, it may be, although it is very unlikely, that the data (pid, name) matches a Python process different from the one that created the lock file. In that case the algorithm fails to recognize the lock file as stale.

History

This is a refactored version of the locking algorithm used by the worker for the Fishtest web application https://tests.stockfishchess.org/tests.