API

Locks

class redlock_plus.Lock(resource_name, connection_details=None, nodes=None, retry_times=3, retry_delay=200, ttl=120000)

A distributed lock implementation based on Redis. It shares the same API as Python’s threading.Lock and behaves mostly the same. Generally this can be used as a drop-in replacement, given the lock is provided from a LockFactory.

The lock supports the context manager protocol, which is the preferred way to use it

with Lock("my_resource"):
    # do some work

# this is equivalent to

lock = Lock("my_resource")
lock.acquire()
# do some stuff
lock.release()
Parameters
  • resource_name (str) – Global identifier to be used for the lock. This will be shared across all redis nodes

  • connection_details (Optional[List[Dict[str, Any]]]) – A list containing either redis client instances or dicts that can be used to create a redis client. If None, nodes must not be None

  • nodes (Optional[List[Redis]]) – A list containing already initialised redis nodes. Takes precedence over connection_details. If None, connection_details must not be None

  • retry_times (int) – Amount of times to retry acquiring a lock after a failed attempt

  • retry_delay (int) – Time in milliseconds between retry attempts to acquire a lock

  • ttl (int) – Time in seconds until the lock should expire. This should be set to a relatively high amount compared to the time it takes to complete the work for which the lock should be held. Default is 120_000 milliseconds (2 minutes)

start_autoextend(timeout=None)

Start an autoextending thread which will attempt to extedn the lock at 3/4 of its expected ttl.

Parameters

timeout (Optional[float]) – Timeout in seconds until the thread terminates. If None, the lock will be extended indefinitely (i.e. as long as the main thread is running)

Return type

Thread

Returns

The autoextending thread

Raises

InvalidOperationError – If the lock was not previously acquired

stop_autoextend()

Stop the autoextending thread

Return type

None

acquire(blocking=True, timeout=- 1, autoextend=True, autoextend_timeout=None)

Attempt to acquire a new lock, blocking or non-blocking.

Parameters
  • blocking (bool) – If True, block until the lock can be acquired

  • timeout (float) – If blocking is True and timeout is a positive value, in the case a request would block, block at most timeout seconds

  • autoextend (bool) – If True start a thread once the lock is acquired that will attempt to extend the lock at 3/4 of its expected ttl

  • autoextend_timeout (Optional[float]) – Timeout in seconds after which the autoextend thread will terminate regardless of the lock status

Return type

float

Returns

A float indicating the minimal time the lock can be considered held in milliseconds in case the lock could be acquired, else False

Raises

ValueError – If blocking is False and timeout is a positive value

extend()

Extend an acquired lock.

Return type

Union[bool, float]

Returns

A float indicating the minimal time the lock can be considered held in milliseconds in case the lock could be acquired, else False

Raises

InvalidOperationError – If the lock was not previously acquired

acquire_or_extend(blocking=True, timeout=- 1, autoextend=True, autoextend_timeout=None)

If the lock is currently not held, try to acquire it. If it is held, extend it. blocking, timeout, autoextend and autoextend_timeout will be passed to Lock.acquire().

Return type

float

Returns

A float indicating the minimal time the lock can be considered held in milliseconds in case the lock could be acquired, else False

check_times()

Check if the lock is still held and the time to live on each node, accounting for clock drift and request time.

Return type

Tuple[bool, List[float]]

Returns

A tuple consisting of a boolean, indicating if the lock can still be considered held and a list of the reported times in milliseconds of each node

Raises

InvalidOperationError – If the lock was not previously acquired

release()

Release the lock. A lock is considered released if the action to release it could be performed sucessfully on the majority of redis nodes. So unlike Python’s threading.Lock this can fail for reasons other than the lock not being held in the first place.

Raises

InvalidOperationError – If the lock was not previously acquired

Return type

bool

Returns

Whether or not the lock was successfully released

locked()

Check if the lock is still held.

Return type

bool

Returns

True if the lock has previously been acquired and the smallest reported time to live of any node is positive, False otherwise.

class redlock_plus.RLock(*args, **kwargs)

A reentrant version of Lock, the only difference being that calls to acquire / release may be nested. Only the final call to acquire actually releases the lock.

acquire(blocking=True, timeout=- 1, autoextend=False, autoextend_timeout=None)

Attempt to acquire a new lock and / or increment the recursion level. If the recursion level was 0, acquire a lock, otherwise just increase the recursion level.

Parameters
  • blocking (bool) – If True, block until the lock can be acquired

  • timeout (float) – If blocking is True and timeout is a positive value, in the case a request would block, block at most timeout seconds

  • autoextend (bool) – If True start a thread once the lock is acquired that will attempt to extend the lock at 3/4 of its expected ttl

  • autoextend_timeout (Optional[float]) – Timeout in seconds after which the autoextend thread will terminate regardless of the lock status

Return type

float

Returns

A float indicating the minimal time the lock can be considered held in milliseconds in case the lock could be acquired, else False

Raises
  • ValueError – If blocking is False and timeout is a positive value

  • RedlockError – The recursion level should be increased but the lock was lost in the meantime

acquire_or_extend(blocking=True, timeout=- 1, autoextend=True, autoextend_timeout=None)

If the lock is currently not held, try to acquire it. If it is held, extend it. blocking, timeout, autoextend and autoextend_timeout will be passed to Lock.acquire(). If a lock was acquired or extended, increase the recursion level.

Return type

float

Returns

A float indicating the minimal time the lock can be considered held in milliseconds in case the lock could be acquired, else False

release()

Release the lock and / or decrement the recursion level. If the recursion level reaches 0 release the lock. A lock is considered released if the action to release it could be performed sucessfully on the majority of redis nodes. So unlike Python’s threading.RLock this can fail for reasons other than the lock not being held in the first place.

Raises

InvalidOperationError – If the lock was not previously acquired

Return type

bool

Returns

Whether or not the lock was successfully released

Helpers

class redlock_plus.LockFactory(connection_details, lock_class=None, **kwargs)

Create new Lock instances from a fixed configuration.

Parameters
  • connection_details (List[Dict[str, Any]]) – An iterable of connection parameters. See Lock for details

  • kwargs (Any) – Default values for keyword arguments to pass to each created Lock instance

class redlock_plus.RLockFactory(connection_details, lock_class=None, **kwargs)

Convenience subclass of LockFactory, to create RLocks.

redlock_plus.init_redis_nodes(connection_details)

Initialise redis nodes by adding lua scripts to release, bump and check locks. If passed a list of dictionaries, create redis.StrictRedis instances from them first.

Return type

List[Redis]

Exceptions

class redlock_plus.RedlockError

Base redlock exception

class redlock_plus.InsufficientNodesError(node_count, *args)

Raised if the minimum amount of 3 nodes was not met

class redlock_plus.InvalidOperationError

An operation was performed on the lock which the current state of the lock does not allow