Python Client

py-ldlm is an LDLM client library providing Python sync and async clients at https://github.com/imoore76/py-ldlm.

Installation

$ pip install py-ldlm

Basic Usage

Below are some basic usage examples.

See also

LDLM Concepts

For a basic understanding of how locks function and the different locking methods available.

LDLM Use Cases

For Python client examples of common use cases.

API Reference

For a complete view of Python client functionality.

Client

Create client
import ldlm

client = ldlm.Client("ldlm-server:3144")
Lock and unlock
lock = client.lock("my-task")

try:
    do_something()
finally:
    lock.unlock()
Context manager
# Lock will be unlocked when context manager exits
with client.lock_context("my-task"):
    do_something()

Async Client

Create async client
import ldlm

client = ldlm.AsyncClient("ldlm-server:3144")
Async lock and unlock
lock = await client.lock("my-task")

try:
    await do_something()
finally:
    await lock.unlock()
Async context manager
# Lock will be unlocked when context manager exits
async with client.lock_context("my-task"):
    await do_something()

TLS Configuration

Using TLS for LDLM client connections involves passing a ldlm.TLSConfig object to the client on instantiation.

Server TLS with cert signed by private CA
import ldlm

client = ldlm.Client("ldlm-server:3144", tls=ldlm.TLSConfig(
    ca_file="/etc/ldlm/certs/ca_cert.pem"
))
Mutual TLS
import ldlm

client = ldlm.Client("ldlm-server:3144", tls=ldlm.TLSConfig(
    cert_file="/etc/ldlm/certs/client_cert.pem",
    key_file="/etc/ldlm/certs/client_cert.pem",
    ca_file="/etc/ldlm/certs/ca_cert.pem"
))

See also

Be sure to set up TLS in the server as described in Server TLS.

API Reference

class ldlm.Client(address: str, password: str | None = None, tls: TLSConfig | None = None, retries: int = -1, retry_delay_seconds: int = 5, auto_renew_locks: bool = True, lock_timeout_seconds: int = 0)

Client class for interacting with the LDLM server.

Parameters:
  • address (str) – The address of the server.

  • password (str, optional) – The password to use for authentication. Defaults to None.

  • tls (TLSConfig, optional) – TLS configuration. Leave None (default) to disable TLS.

  • retries (int, optional) – The number of retries to attempt. Defaults to -1 (infinite). Set to 0 to disable retries

  • retry_delay_seconds (int, optional) – The delay in seconds between retry attempts.

  • auto_renew_locks (bool, optional) – Automatically renew locks using a background thread or asyncio task

  • lock_timeout (int, optional) – The lock timeout to use for all lock operations

lock(name: str, wait_timeout_seconds: int = 0, lock_timeout_seconds: int | None = None, size: int = 0) Lock

Acquire a lock with the given name.

If the client’s auto_renew_lock parameter was set to True (the default) or left unspecified, the lock will be automatically renewed at an appropriate interval using a background thread.

Parameters:
  • name (str) – The name of the lock to acquire.

  • wait_timeout_seconds (int, optional) – The timeout in seconds to wait for the lock to be acquired. Defaults to 0 (wait indefinitely).

  • lock_timeout_seconds (int, optional) – The timeout in seconds after which the lock will be released unless it is renewed. Defaults to None (no timeout).

  • size (int, optional) – The size of the lock. Defaults to 0 which translates to unspecified. The server will use a size of 1 in this case.

Returns:

The lock object.

Return type:

Lock

Raises:

Examples

>>> from ldlm import Client
>>>
>>> client = Client("ldlm-server:3144")
>>>
>>> lock = client.lock(
...     "my_lock",
...     wait_timeout_seconds=10,
...     lock_timeout_seconds=600,
... )
>>> if not lock:
...     print("Could not acquire lock within 10 seconds")
... else:
...     print("Doing work with lock")
...     try:
...         pass // do work
...     finally:
...         lock.unlock()
...         print("Released lock")
...
Doing work with lock
Released lock
>>> # This blocks until lock is obtained.
>>> lock = client.lock(
...     "my_lock",
...     lock_timeout_seconds=600,
... )
>>>
>>> print("Lock obtained")
Lock obtained
>>> # Do work with lock
>>> try:
...     pass // do work
... finally:
...     lock.unlock()
...     print("Released lock")
>>> Released lock
lock_context(name: str, wait_timeout_seconds: int = 0, lock_timeout_seconds: int | None = None, size: int = 0) Iterator[Lock]

A context manager that acquires a lock with the given name and unlocks the lock when the context is exited.

If the client’s auto_renew_lock parameter was set to True (the default) or left unspecified, the lock will be automatically renewed at an appropriate interval using a background thread.

Parameters:
  • name (str) – The name of the lock to acquire.

  • wait_timeout_seconds (int, optional) – The timeout in seconds to wait for the lock to be acquired. Defaults to 0 (wait indefinitely).

  • lock_timeout_seconds (int, optional) – The timeout in seconds after which the lock will be released unless it is renewed. Defaults to None (no timeout).

  • size (int, optional) – The size of the lock. Defaults to 0 which translates to unspecified. The server will use a size of 1 in this case.

Yields:

Lock – The lock object.

Raises:

Examples

>>> from ldlm import Client
>>>
>>> client = Client("ldlm-server:3144")
>>>
>>> with client.lock_context(
...     "my_lock",
...     wait_timeout_seconds=10,
...     lock_timeout_seconds=600,
... ) as lock:
...     if not lock:
...         print("Could not acquire lock")
...     else:
...         print("Doing work with lock...")
...         pass
...         print("Done")
...
Doing work with lock...
Done
try_lock(name: str, lock_timeout_seconds: int | None = None, size: int = 0) Lock

Attempts to acquire a lock and immediately returns; whether the lock was acquired or not. Inspect the returned lock’s locked property or evaluate the lock as a boolean value to determine if it was acquired.

If the client’s auto_renew_lock parameter was set to True (the default) or left unspecified, the lock will be automatically renewed at an appropriate interval using a background thread.

Parameters:
  • name (str) – The name of the lock to acquire.

  • lock_timeout_seconds (int, optional) – The timeout in seconds after which the lock will be released unless it is renewed. Defaults to None (no timeout).

  • size (int, optional) – The size of the lock. Defaults to 0 which translates to unspecified. The server will use a size of 1 in this case.

Returns:

The lock object.

Return type:

Lock

Raises:

Examples

>>> from ldlm import Client
>>>
>>> client = Client("ldlm-server:3144")
>>>
>>> lock = client.try_lock(
...     "my_lock",
...     lock_timeout_seconds=600,
... )
>>> if not lock:
...     print("Could not acquire lock")
... else:
...     print("Doing work with lock")
...     try:
...         pass // do work
...     finally:
...         lock.unlock()
...         print("Released lock")
...
Doing work with lock
Released lock
try_lock_context(name: str, lock_timeout_seconds: int | None = None, size: int = 0) Iterator[Lock]

A context manager that attempts to acquire a lock with the given name. You must inspect the returned lock’s locked property or evaluate it as a boolean value to determine if it was acquired. The lock is unlocked automatically when the context is exited.

If the client’s auto_renew_lock parameter was set to True (the default) or left unspecified, the lock will be automatically renewed at an appropriate interval using a background thread.

Parameters:
  • name (str) – The name of the lock to acquire.

  • lock_timeout_seconds (int, optional) – The timeout in seconds after which the lock will be released unless it is renewed. Defaults to 0 (no timeout).

  • size (int, optional) – The size of the lock. Defaults to 0 which translates to unspecified. The server will use a size of 1 in this case.

Yields:

Lock – The lock object.

Raises:

Examples

>>> from ldlm import Client
>>>
>>> client = Client("ldlm-server:3144")
>>>
>>> with client.try_lock_context(
...     "my_lock",
...     lock_timeout_seconds=600,
... ) as lock:
...     if not lock:
...         print("Could not acquire lock")
...     else:
...         print("Doing work with lock...")
...         pass
...         print("Done")
...
Doing work with lock...
Done
renew(name: str, key: str, lock_timeout_seconds: int) Lock

Renews a lock. It is much more concise to run this method on the ldlm.Lock object returned by this client’s lock methods.

Parameters:
  • name (str) – The name of the lock to renew.

  • key (str) – The key associated with the lock to renew.

  • lock_timeout_seconds (int) – The timeout in seconds for acquiring the lock.

Returns:

A lock object.

Return type:

Lock

Raises:

ldlm.exceptions.LockDoesNotExistOrInvalidKeyError – If the lock does not exist or the lock key is invalid.

unlock(name: str, key: str) None

Unlock the lock with the specified name and key. It is much more concise to run this method on the ldlm.Lock object returned by this client’s lock methods.

Parameters:
  • name (str) – The name of the lock to unlock.

  • key (str) – The key associated with the lock to unlock.

Raises:
Returns:

None

close() None

Closes the LDLM gRPC channel.

This method is used to close the LDLM gRPC channel and indicate that the client is no longer active. It is typically called when the client is no longer needed or when the program is exiting.

Returns:

None

class ldlm.Lock(client: Client, lock: LockResponse)

A lock returned by LDLM Client lock methods.

Parameters:
  • client (Client) – The client object.

  • lock (pb.LockResponse) – An LDLM lock response object.

name: str

name of the lock

key: str

key associated with the lock

locked: bool

whether the lock is locked or not

unlock() None

Unlocks the lock.

Returns:

None

Raises:

RuntimeError – If the lock is not locked

renew(lock_timeout_seconds: int) None

Renews the lock.

Parameters:

lock_timeout_seconds (int) – The timeout in seconds after which the lock will expire.

Returns:

None

Raises:

RuntimeError – If the lock is not locked

class ldlm.AsyncClient(address: str, password: str | None = None, tls: TLSConfig | None = None, retries: int = -1, retry_delay_seconds: int = 5, auto_renew_locks: bool = True, lock_timeout_seconds: int = 0)

asyncio client class for interacting with the LDLM server.

Parameters:
  • address (str) – The address of the server.

  • password (str, optional) – The password to use for authentication. Defaults to None.

  • tls (TLSConfig, optional) – TLS configuration. Leave None (default) to disable TLS.

  • retries (int, optional) – The number of retries to attempt. Defaults to -1 (infinite). Set to 0 to disable retries

  • retry_delay_seconds (int, optional) – The delay in seconds between retry attempts.

  • auto_renew_locks (bool, optional) – Automatically renew locks using a background thread or asyncio task

  • lock_timeout (int, optional) – The lock timeout to use for all lock operations

async lock(name: str, wait_timeout_seconds: int = 0, lock_timeout_seconds: int | None = None, size: int = 0) AsyncLock

Acquires a lock with the given name.

If the client’s auto_renew_lock parameter was set to True (the default) or left unspecified, the lock will be automatically renewed at an appropriate interval using a background asyncio task.

Parameters:
  • name (str) – The name of the lock to acquire.

  • wait_timeout_seconds (int, optional) – The timeout in seconds to wait for the lock to be acquired. Defaults to 0 (wait indefinitely).

  • lock_timeout_seconds (int, optional) – The timeout in seconds after which the lock will be released unless it is renewed. Defaults to None (no timeout).

  • size (int, optional) – The size of the lock. Defaults to 0 which translates to unspecified. The server will use a size of 1 in this case.

Returns:

A lock object.

Return type:

AsyncLock

Examples

>>> import asyncio
>>> from ldlm import AsyncClient
>>>
>>> async def test_lock():
...     client = AsyncClient("ldlm-server:3144")
...     lock = await client.lock(
...             "test_lock",
...             wait_timeout_seconds=10,
...             lock_timeout_seconds=600,
...     )
...     if not lock:
...         print("Could not acquire lock within 10 seconds")
...         return
...     print("Doing work with lock")
...     try:
...         pass # do some work with the lock
...     finally:
...         await lock.unlock()
...         print("Released lock")
...
>>> asyncio.run(test_lock())
Doing work with lock
Released lock
lock_context(name: str, wait_timeout_seconds: int = 0, lock_timeout_seconds: int | None = None, size: int = 0) AsyncIterator[AsyncLock]

A context manager that acquires a lock and unlocks it when the context is exited.

If the client’s auto_renew_lock parameter was set to True (the default) or left unspecified, the lock will be automatically renewed at an appropriate interval using a background asyncio task.

Parameters:
  • name (str) – The name of the lock to acquire.

  • wait_timeout_seconds (int, optional) – The timeout in seconds to wait for the lock to be acquired. Defaults to 0 (wait indefinitely).

  • lock_timeout_seconds (int, optional) – The timeout in seconds after which the lock will be released unless it is renewed. Defaults to 0 (no timeout).

  • size (int, optional) – The size of the lock. Defaults to 0 which translates to unspecified. The server will use a size of 1 in this case.

Yields:

AsyncLock – A lock object.

Raises:

RuntimeError – If the lock cannot be released after being acquired.

Examples

>>> import asyncio
>>> from ldlm import AsyncClient
>>>
>>> async def test_lock_context():
...     client = AsyncClient("ldlm-server:3144")
...
...     async with client.lock_context(
...             "my_lock",
...             wait_timeout_seconds=10,
...             lock_timeout_seconds=600,
...     ) as lock:
...         if not lock:
...             print("Could not acquire lock within 10 seconds")
...         print("Doing work with lock")
...
>>> asyncio.run(test_lock_context())
Doing work with lock
async try_lock(name: str, lock_timeout_seconds: int | None = None, size: int = 0) AsyncLock

Attempts to acquire a lock and immediately returns; whether the lock was acquired or not. You must inspect the returned lock’s locked property or evaluate it as a boolean value to determine if it was acquired.

If the client’s auto_renew_lock parameter was set to True (the default) or left unspecified, the lock will be automatically renewed at an appropriate interval using a background asyncio task.

Parameters:
  • name (str) – The name of the lock to acquire.

  • lock_timeout_seconds (int, optional) – The timeout in seconds after which the lock will be released unless it is renewed. Defaults to None (no timeout).

  • size (int, optional) – The size of the lock. Defaults to 0 which translates to unspecified. The server will use a size of 1 in this case.

Yields:

AsyncLock – A lock object.

Raises:

RuntimeError – If the lock cannot be released after being acquired.

Examples

>>> async def test_try_lock():
...     client = AsyncClient("ldlm-server:3144")
...
...     lock = await client.try_lock(
...             "my_lock",
...             lock_timeout_seconds=600,
...     )
...     if not lock:
...         print("Could not acquire lock")
...         return
...     print("Doing work with lock")
...     try:
...         pass # do some work with the lock
...     finally:
...         await lock.unlock()
...         print("Released lock")
...
>>> asyncio.run(test_try_lock())
Doing work with lock
Released lock
try_lock_context(name: str, lock_timeout_seconds: int | None = None, size: int = 0) AsyncIterator[AsyncLock]

A context manager that attempts to acquire a lock with the given name. You must inspect the returned lock’s locked property or evaluate it as a boolean value to determine if it was acquired. If locked, the lock will be released when the context is exited.

If the client’s auto_renew_lock parameter was set to True (the default) or left unspecified, the lock will be automatically renewed at an appropriate interval using a background asyncio task.

Parameters:
  • name (str) – The name of the lock to acquire.

  • lock_timeout_seconds (int, optional) – The timeout in seconds after which the lock will be released unless it is renewed. Defaults to None (no timeout).

  • size (int, optional) – The size of the lock. Defaults to 0 which translates to unspecified. The server will use a size of 1 in this case.

Yields:

AsyncLock – A lock object.

Raises:

RuntimeError – If the lock cannot be released after being acquired.

Examples

>>> async def test_try_lock_context():
...     client = AsyncClient("ldlm-server:3144")
...
...     async with client.try_lock_context(
...             "my_lock",
...             lock_timeout_seconds=600,
...     ) as lock:
...         if not lock:
...             print("Could not acquire lock")
...         print("Doing work with lock")
...
>>> asyncio.run(test_try_lock_context())
Doing work with lock
async unlock(name: str, key: str) None

Unlock the specified lock. It is much more concise to run this method on the ldlm.AsyncLock object returned by this client’s lock methods.

Parameters:
  • name (str) – The name of the lock to unlock.

  • key (str) – The key associated with the lock to unlock.

Raises:

RuntimeError – If the lock cannot be unlocked.

async renew(name: str, key: str, lock_timeout_seconds: int) AsyncLock

Renews a lock. It is much more concise to run this method on the ldlm.AsyncLock object returned by this client’s lock methods.

Parameters:
  • name (str) – The name of the lock to renew.

  • key (str) – The key associated with the lock to renew.

  • lock_timeout_seconds (int) – The timeout in seconds for acquiring the lock.

Returns:

A lock object.

Return type:

AsyncLock

async close() None

Closes the LDLM gRPC channel.

This method is used to close the LDLM gRPC channel and indicate that the client is no longer active. It is typically called when the client is no longer needed or when the program is exiting.

Returns:

None

async aclose() None

Awaits self.close(). For compatibility with contextlib.aclosing().

Returns:

None

class ldlm.AsyncLock(client: AsyncClient, lock: LockResponse)

A lock returned by LDLM AsyncClient lock methods.

Parameters:
  • client (Client) – The client object.

  • lock (pb.LockResponse) – An LDLM lock response object.

name: str

name of the lock

key: str

key associated with the lock

locked: bool

whether the lock is locked or not

async unlock() None

Unlocks the lock.

Returns:

None

Raises:

RuntimeError – If the lock is not locked

async renew(lock_timeout_seconds: int) None

Renews the lock.

Parameters:

lock_timeout_seconds (int) – The timeout in seconds after which the lock will expire.

Returns:

None

Raises:

RuntimeError – If the lock is not locked

TLS Config

class ldlm.TLSConfig(cert_file: str | None = None, key_file: str | None = None, ca_file: str | None = None)

TLS configuration dataclass for LDLM client. Pass an instance of this class as the tls parameter to an LDLM client constructor.

cert_file: str | None = None

Path to the client certificate file

key_file: str | None = None

Path to the client key file

ca_file: str | None = None

Path to the CA certificate file

Exceptions

Exception classes for the LDLM service.

exception ldlm.exceptions.LDLMError(message)

Generic LDLM error.

exception ldlm.exceptions.LockDoesNotExistError(message)

Lock does not exist error. This can occur when attempting to unlock or renew a lock that does not exist.

exception ldlm.exceptions.InvalidLockKeyError(message)

Invalid lock key error. The key specified in the request is not valid.

exception ldlm.exceptions.LockWaitTimeoutError(message)

Lock wait timeout error. The lock could not be acquired in wait_timeout_seconds seconds.

exception ldlm.exceptions.NotLockedError(message)

Lock is not locked error. This can occur when attempting to renew or unlock a lock that is not locked.

exception ldlm.exceptions.LockDoesNotExistOrInvalidKeyError(message)

Lock does not exist or invalid key error. This can occur when renewing a lock using an invalid name or key.

exception ldlm.exceptions.LockSizeMismatchError(message)

The size of the lock in the LDLM server does not match the size specified. A previous lock request was made with a different size.

exception ldlm.exceptions.InvalidLockSizeError(message)

The specified size in the lock request is not a valid size (must be > 0).