Module se_lock.

SELock

The SELock is a shared/exclusive lock that you can use to coordinate read and write access to a resource in a multithreaded application.

The SELock does not actually protect resources directly. Instead, the SELock provide coordination by blocking threads when they request a lock that is currently owned by another thread. That means the application must ensure it appropriately requests the lock before attempting to read from or write to a resource.

The application must first instatiate an SELock object that can be accessed by the threads in the application. When a thread wants to read from a resource, it requests the lock for shared mode. If the lock is currently not held or is currently held in shared mode by other threads, and no threads are waiting for the lock for exclusive mode, the new request is granted immediately. For all other cases, the request is queued and blocked until the lock become available.

When a thread wants to write to a resource, it requests the lock in exclusive mode. If the lock is currently not held, the new request is granted immediately. For all other cases, the request is queued and blocked until the lock become available.

The application can instantiate a single lock to protect any number of resources, or several locks for more granularity where each lock can protect a single resource. The application design will need to consider performance (more granularity may perform better) and reliability (more granularity may lead to deadlock situtations).

The SELock provide two ways to use the lock:
  1. methods obtain_excl, obtain_share, and release

  2. context managers SELockExcl, SELockShare, and SELockObtain

Example:

use methods obtain_excl, obtain_share, and release to coordinate access to a resource

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in exclusive mode
>>> a_lock.obtain_excl()
>>> print('lock obtained in exclusive mode')
lock obtained in exclusive mode
>>> # release the lock
>>> a_lock.release()
>>> # Get lock in shared mode
>>> a_lock.obtain_share()
>>> print('lock obtained in shared mode')
lock obtained in shared mode
>>> # release the lock
>>> a_lock.release()
Example:

use SELockExcl and SELockShare context managers to coordinate access to a resource

>>> from scottbrian_locking.se_lock import (SELock, SELockExcl,
...                                         SELockShare)
>>> a_lock = SELock()
>>> # Get lock in exclusive mode
>>> with SELockExcl(a_lock):  # write access
...     msg = 'lock obtained exclusive'
>>> print(msg)
lock obtained exclusive
>>> # Get lock in shared mode
>>> with SELockShare(a_lock):  # read access
...     msg = 'lock obtained shared'
>>> print(msg)
lock obtained shared
Example:

use SELockObtain context managers to coordinate access to a resource

>>> from scottbrian_locking.se_lock import (SELock, SELockObtain,
...                                         SELockObtainMode)
>>> a_lock = SELock()
>>> # Get lock in exclusive mode
>>> with SELockObtain(a_lock, SELockObtainMode.Exclusive):  # write
...     msg = 'lock obtained exclusive'
>>> print(msg)
lock obtained exclusive
>>> # Get lock in shared mode
>>> with SELockObtain(a_lock, SELockObtainMode.Share):  # read
...     msg = 'lock obtained shared'
>>> print(msg)
lock obtained shared
class se_lock.SELock

Provides a share/exclusive lock.

The SELock class is used to coordinate read/write access to resources in a multithreaded application.

Initialize an instance of the SELock class.

Example:

instantiate an SELock

>>> from scottbrian_locking.se_lock import SELock
>>> se_lock = SELock()
>>> print(se_lock)
SELock()
class ReqType(*values)

Enum class for request type.

get_info()

Return a list of the queue items.

Return type:

LockInfo

Returns:

List of queue items.

obtain_excl(timeout=None)

Method to obtain the SELock.

Parameters:

timeout (Union[int, float, None]) – number of seconds that the request is allowed to wait for the lock before an error is raised

Raises:
  • SELockOwnerNotAlive – The owner of the SELock is not alive and will thus never release the lock. Unfortunately, this error is not detectable until a request is made, and raising it here makes the current requestor is an innocent bystander. The solution is to provide recovery processing for lock owners to ensure that resources are left in a known state and held locks are released when the owner thread suffers an error.

  • SELockObtainTimeout – A lock obtain request has timed out waiting for the current owner thread to release the lock.

Example:

obtain an SELock in exclusive mode

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> a_lock.obtain_excl()
>>> print('lock obtained in exclusive mode')
lock obtained in exclusive mode
:rtype: :sphinx_autodoc_typehints_type:`\:py\:obj\:\`None\``
obtain_excl_recursive(timeout=None)

Method to obtain the SELock recursive mode.

Parameters:

timeout (Union[int, float, None]) – number of seconds that the request is allowed to wait for the lock before an error is raised

Raises:
  • SELockOwnerNotAlive – The owner of the SELock is not alive and will thus never release the lock. Unfortunately, this error is not detectable until a request is made, and raising it here makes the current requestor is an innocent bystander. The solution is to provide recovery processing for lock owners to ensure that resources are left in a known state and held locks are released when the owner thread suffers an error.

  • SELockObtainTimeout – A lock obtain request has timed out waiting for the current owner thread to release the lock.

Example:

obtain an SELock in exclusive mode

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> a_lock.obtain_excl_recursive()
>>> print('lock obtained in exclusive mode')
lock obtained in exclusive mode
:rtype: :sphinx_autodoc_typehints_type:`\:py\:obj\:\`None\``
obtain_share(timeout=None)

Method to obtain the SELock.

Parameters:

timeout (Union[int, float, None]) – number of seconds that the request is allowed to wait for the lock before an error is raised

Raises:
  • SELockOwnerNotAlive – The owner of the SELock is not alive and will thus never release the lock. Unfortunately, this error is not detectable until a request is made, and raising it here makes the current requestor is an innocent bystander. The solution is to provide recovery processing for lock owners to ensure that resources are left in a known state and held locks are released when the owner thread suffers an error.

  • SELockObtainTimeout – A lock obtain request has timed out waiting for the current owner thread to release the lock.

Example:

obtain an SELock in exclusive mode

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> a_lock.obtain_share()
>>> print('lock obtained in shared mode')
lock obtained in shared mode
:rtype: :sphinx_autodoc_typehints_type:`\:py\:obj\:\`None\``
release()

Method to release the SELock.

Raises:
  • AttemptedReleaseOfUnownedLock – A release of the SELock was attempted by thread {threading.current_thread()} but an entry on the owner-waiter queue was not found for that thread.

  • AttemptedReleaseByExclusiveWaiter – A release of the SELock was attempted by thread {threading.current_thread()} but the entry found was still waiting for exclusive control of the lock.

  • AttemptedReleaseBySharedWaiter – A release of the SELock was attempted by thread {threading.current_thread()} but the entry found was still waiting for shared control of the lock.

Example:

obtain an SELock in shared mode and release it

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> a_lock.obtain_share()
>>> print('lock obtained in shared mode')
lock obtained in shared mode
>>> a_lock.release()
>>> print('lock released')
lock released
Return type:

None

verify_lock(exp_q=None, exp_owner_count=None, exp_excl_wait_count=None, timeout=None, verify_structures=True)

Verifies that the lock is in the specified state.

Parameters:
  • exp_q (Optional[list[LockItem]]) – list of LockItem objects that specify the expected owners and/or waiters of the lock. If not specified, the lock will be do a minimal verification to ensure the counts are reasonable.

  • exp_owner_count (Optional[int]) – specifies the expected owner count. If specified, exp_q must also be specified.

  • exp_excl_wait_count (Optional[int]) – specifies the expected exclusive wait count. If specified, exp_q must also be specified.

  • timeout (Union[int, float, None]) – A non-zero positive value specifies the time allowed for the lock to get into the specified state. If not specified or a value of zero or less is specified, the lock must already be in the specified state at entry. Note that exp_q must also be specified.

  • verify_structures (bool) – If True, verify the lock structures

Raises:

LockVerifyError – the lock failed to verify, or failed to reach the expected state within the time specified for timeout.

Return type:

None

class se_lock.SELockExcl(se_lock, obtain_tf=True, allow_recursive_obtain=False, timeout=None)

Context manager for exclusive control.

Initialize exclusive lock context manager.

Parameters:
  • se_lock (SELock) – instance of SELock

  • obtain_tf (bool) – allows the obtain to be conditional to allow coding the with statement and then getting or not getting the lock as dynamically required

  • allow_recursive_obtain (bool) – if lock is already owned by the requesting thread, simply bump the ownership count

  • timeout (Union[int, float, None]) – number of seconds that the request is allowed to wait for the lock before an error is raised

Raises:
  • SELockOwnerNotAlive – The owner of the SELock is not alive and will thus never release the lock. Unfortunately, this error is not detectable until a request is made, and raising it here makes the current requestor is an innocent bystander. The solution is to provide recovery processing for lock owners to ensure that resources are left in a known state and held locks are released when the owner thread suffers an error.

  • SELockObtainTimeout – A lock obtain request has timed out waiting for the current owner thread to release the lock.

Example:

obtain an SELock in exclusive mode

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in exclusive mode
>>> with SELockExcl(a_lock):
...     msg = 'lock obtained exclusive'
>>> print(msg)
lock obtained exclusive
Example:

obtain an SELock in exclusive mode conditionally

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in exclusive mode conditionally
>>> condition_var = True
>>> with SELockExcl(a_lock, obtain_tf=condition_var):
...     if condition_var:
...         msg = 'lock obtained exclusive'
...     else:
...         msg = 'lock not obtained'
>>> print(msg)
lock obtained exclusive
Example:

obtain an SELock in exclusive mode conditionally

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in exclusive mode conditionally
>>> condition_var = False
>>> with SELockExcl(a_lock, obtain_tf=condition_var):
...     if condition_var:
...         msg = 'lock obtained exclusive'
...     else:
...         msg = 'lock not obtained'
>>> print(msg)
lock not obtained
class se_lock.SELockObtain(se_lock, obtain_mode, obtain_tf=True, allow_recursive_obtain=False, timeout=None)

Context manager for shared or exclusive control.

Initialize shared or exclusive lock context manager.

Parameters:
  • se_lock (SELock) – instance of SELock

  • obtain_mode (SELockObtainMode) – specifies the lock mode required as Share or Exclusive

  • obtain_tf (bool) – allows the obtain to be conditional to allow coding the with statement and then getting or not getting the lock as dynamically required

  • allow_recursive_obtain (bool) – if lock is already owned by the requesting thread, simply bump the ownership count

  • timeout (Union[int, float, None]) – number of seconds that the request is allowed to wait for the lock before an error is raised

Raises:
  • SELockOwnerNotAlive – The owner of the SELock is not alive and will thus never release the lock. Unfortunately, this error is not detectable until a request is made, and raising it here makes the current requestor is an innocent bystander. The solution is to provide recovery processing for lock owners to ensure that resources are left in a known state and held locks are released when the owner thread suffers an error.

  • SELockObtainTimeout – A lock obtain request has timed out waiting for the current owner thread to release the lock.

Example:

obtain an SELock in exclusive mode

>>> from scottbrian_locking.se_lock import (SELock,
...                                         SELockObtain,
...                                         SELockObtainMode)
>>> a_lock = SELock()
>>> # Get lock in exclusive mode
>>> with SELockObtain(a_lock,
...                   obtain_mode=SELockObtainMode.Exclusive):
...     msg = 'lock obtained exclusive'
>>> print(msg)
lock obtained exclusive
Example:

obtain an SELock in shared mode

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in shared mode
>>> with SELockObtain(a_lock,
...                   obtain_mode=SELockObtainMode.Share):
...     msg = 'lock obtained shared'
>>> print(msg)
lock obtained shared
Example:

obtain an SELock in exclusive mode conditionally

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in exclusive mode conditionally
>>> condition_var = True
>>> with SELockObtain(a_lock,
...                   obtain_mode=SELockObtainMode.Exclusive,
...                   obtain_tf=condition_var):
...     if condition_var:
...         msg = 'lock obtained exclusive'
...     else:
...         msg = 'lock was not obtained'
>>> print(msg)
lock obtained exclusive
Example:

obtain an SELock in exclusive mode conditionally

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in exclusive mode conditionally
>>> condition_var = False
>>> with SELockObtain(a_lock,
...                   obtain_mode=SELockObtainMode.Exclusive,
...                   obtain_tf=condition_var):
...     if condition_var:
...         msg = 'lock obtained exclusive'
...     else:
...         msg = 'lock was not obtained'
>>> print(msg)
lock was not obtained
Example:

obtain an SELock in shared mode conditionally

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in shared mode conditionally
>>> condition_var = True
>>> with SELockObtain(a_lock,
...                   obtain_mode=SELockObtainMode.Share,
...                   obtain_tf=condition_var):
...     if condition_var:
...         msg = 'lock obtained shared'
...     else:
...         msg = 'lock was not obtained'
>>> print(msg)
lock obtained shared
Example:

obtain an SELock in shared mode conditionally

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in shared mode conditionally
>>> condition_var = False
>>> with SELockObtain(a_lock,
...                   obtain_mode=SELockObtainMode.Share,
...                   obtain_tf=condition_var):
...     if condition_var:
...         msg = 'lock obtained shared'
...     else:
...         msg = 'lock was not obtained'
>>> print(msg)
lock was not obtained
class se_lock.SELockShare(se_lock, obtain_tf=True, timeout=None)

Context manager for shared control.

Initialize shared lock context manager.

Parameters:
  • se_lock (SELock) – instance of SELock

  • obtain_tf (bool) – allows the obtain request to be conditional to allow coding the with statement and then getting or not getting the lock as dynamically required

  • timeout (Union[int, float, None]) – number of seconds that the request is allowed to wait for the lock before an error is raised

Raises:
  • SELockOwnerNotAlive – The owner of the SELock is not alive and will thus never release the lock. Unfortunately, this error is not detectable until a request is made, and raising it here makes the current requestor is an innocent bystander. The solution is to provide recovery processing for lock owners to ensure that resources are left in a known state and held locks are released when the owner thread suffers an error.

  • SELockObtainTimeout – A lock obtain request has timed out waiting for the current owner thread to release the lock.

Example:

obtain an SELock in shared mode

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in shared mode
>>> with SELockShare(a_lock):
...     msg = 'lock obtained shared'
>>> print(msg)
lock obtained shared
Example:

obtain an SELock in shared mode conditionally

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in shared mode conditionally
>>> condition_var = True
>>> with SELockShare(a_lock, obtain_tf=condition_var):
...     if condition_var:
...         msg = 'lock obtained shared'
...     else:
...         msg = 'lock not obtained'
>>> print(msg)
lock obtained shared
Example:

obtain an SELock in shared mode conditionally

>>> from scottbrian_locking.se_lock import SELock
>>> a_lock = SELock()
>>> # Get lock in shared mode conditionally
>>> condition_var = False
>>> with SELockShare(a_lock, obtain_tf=condition_var):
...     if condition_var:
...         msg = 'lock obtained shared'
...     else:
...         msg = 'lock not obtained'
>>> print(msg)
lock not obtained