Source code for postgresql.alock

##
# .alock - Advisory Locks
##
"""
Tools for Advisory Locks
"""
from abc import abstractmethod, abstractproperty
from .python.element import Element

__all__ = [
	'ALock',
	'ExclusiveLock',
	'ShareLock',
]

[docs]class ALock(Element): """ Advisory Lock class for managing the acquisition and release of a sequence of PostgreSQL advisory locks. ALock()'s are fairly consistent with threading.RLock()'s. They can be acquired multiple times, and they must be released the same number of times for the lock to actually be released. A notably difference is that ALock's manage a sequence of lock identifiers. This means that a given ALock() may represent multiple advisory locks. """ _e_factors = ('database', 'identifiers',) _e_label = 'ALOCK' def _e_metas(self, headfmt = "{1} [{0}]".format ): yield None, headfmt(self.state, self.mode) @abstractproperty
[docs] def mode(self): """ The mode of the lock class. """
@abstractproperty def __select_statements__(self): """ Implemented by subclasses to return the statements to try, acquire, and release the advisory lock. Returns a triple of callables where each callable takes two arguments, the lock-id pairs, and then the int8 lock-ids. ``(try, acquire, release)``. """ @staticmethod def _split_lock_identifiers(idseq): # lame O(2) id_pairs = [ list(x) if x.__class__ is not int else [None,None] for x in idseq ] ids = [ x if x.__class__ is int else None for x in idseq ] return (id_pairs, ids)
[docs] def acquire(self, blocking = True, len = len): """ Acquire the locks using the configured identifiers. """ if self._count == 0: # _count is zero, so the locks need to be acquired. wait = bool(blocking) if wait: self._acquire(self._id_pairs, self._ids) else: # grab the success of each lock id. if some were # unsuccessful, then the ones that were successful need to be # released. r = self._try(self._id_pairs, self._ids) # accumulate the identifiers that *did* lock release_seq = [ id for didlock, id in zip(r, self.identifiers) if didlock[0] ] if len(release_seq) != len(self.identifiers): # some failed, so release the acquired and return False # # reverse in case there is another waiting for all. # that is, release last-to-first so that if another is waiting # on the same seq that it should be able to acquire all of # them once the contended lock is released. release_seq.reverse() self._release(*self._split_lock_identifiers(release_seq)) # unable to acquire all. return False self._count = self._count + 1 return True
def __enter__(self): self.acquire() return self
[docs] def release(self): """ Release the locks using the configured identifiers. """ if self._count < 1: raise RuntimeError("cannot release un-acquired lock") if not self.database.closed and self._count > 0: # if the database has been closed, or the count will # remain non-zero, there is no need to release. self._release(reversed(self._id_pairs), reversed(self._ids)) # decrement the count nonetheless. self._count = self._count - 1
def __exit__(self, typ, val, tb): self.release()
[docs] def locked(self): """ Whether the locks have been acquired. This method is sensitive to the connection's state. If the connection is closed, it will return False. """ return (self._count > 0) and (not self.database.closed)
@property def state(self): return 'locked' if self.locked() else 'unlocked' def __init__(self, database, *identifiers): """ Initialize the lock object to manage a sequence of advisory locks for use with the given database. """ self._count = 0 self.connection = self.database = database self.identifiers = identifiers self._id_pairs, self._ids = self._split_lock_identifiers(identifiers) self._try, self._acquire, self._release = self.__select_statements__()
class ShareLock(ALock): mode = 'share' def __select_statements__(self): return ( self.database.sys.try_advisory_shared, self.database.sys.acquire_advisory_shared, self.database.sys.release_advisory_shared, ) class ExclusiveLock(ALock): mode = 'exclusive' def __select_statements__(self): return ( self.database.sys.try_advisory_exclusive, self.database.sys.acquire_advisory_exclusive, self.database.sys.release_advisory_exclusive, )