def release(self): """Release a lock :returns: True if the lock was released, or False if it is no longer valid. :rtype: bool """ self._revoked = [] try: safe_call(self._zk, 'delete', self._candidate_path) return True except (zookeeper.NoNodeException, AttributeError): return False
def clear(self): """Clear out a lock .. warning:: You must be sure this is a dead lock, as clearing it will forcibly release it by deleting all lock nodes. :returns: True if the lock was cleared, or False if it is no longer valid. :rtype: bool """ children = safe_call(self._zk, 'get_children', self._locknode) for child in children: try: safe_call(self._zk, 'delete', self._locknode + '/' + child) except zookeeper.NoNodeException: pass
def _ensure_lock_dir(self): # Ensure our lock dir exists if safe_call(self._zk, 'exists', self._locknode): return try: safe_call(self._zk, 'create', self._lock_root, "zktools ZLock dir", [ZOO_OPEN_ACL_UNSAFE], 0) except zookeeper.NodeExistsException: if self._log_debug: log.debug("Lock node in Zookeeper already created") # Try and create our locking node try: safe_call(self._zk, 'create', self._locknode, "lock", [ZOO_OPEN_ACL_UNSAFE], 0) except zookeeper.NodeExistsException: # Ok if this exists already pass
def revoke_watcher(handle, type, state, path): # This method must be in closure scope to ensure that # it can append to the thread it is called from # to indicate if this particular thread's lock was # revoked or removed if type == zookeeper.CHANGED_EVENT: data = safe_call(self._zk, 'get', path, revoke_watcher)[0] if data == 'unlock': self._revoked.append(True) elif type == zookeeper.DELETED_EVENT or \ state == zookeeper.EXPIRED_SESSION_STATE: # Trigger if node was deleted self._revoked.append(True)
def revoke_all(self): """Revoke any existing locks, gently Unlike :meth:`clear`, this asks all existing locks to release, rather than forcibly revoking them. :returns: True if existing locks were present, False if there were no existing locks. :rtype: bool """ # Get all the children of the node children = safe_call(self._zk, 'get_children', self._locknode) if not children: return False for child in children: try: safe_call(self._zk, 'set', self._locknode + '/' + child, "unlock") except zookeeper.NoNodeException: pass return True
def __init__(self, connection, lock_name, lock_root='/ZktoolsLocks'): """Create an Asynchronous Zookeeper Lock :param connection: Zookeeper connection object :type connection: zc.zk Zookeeper instance :param lock_name: Path to the lock node that should be used :param lock_root: Path to the root lock node to create the locks under :type lock_root: string """ self._zk = connection self._lock_path = '/'.join([lock_root, lock_name]) self._lock_event = threading.Event() self._acquired = False self._candidate_path = None self._acquire_func = self._release_func = None self.errors = [] try: safe_call(self._zk, 'create_recursive', self._lock_path, "zktools ZLock dir", [ZOO_OPEN_ACL_UNSAFE]) except zookeeper.NodeExistsException: pass
def has_lock(self): """Check with Zookeeper to see if the lock is acquired :returns: Whether the lock is acquired or not :rtype: bool """ if not self._candidate_path: # So we can check it even if we released return False znode = self._candidate_path keyname = znode[znode.rfind('/') + 1:] # Get all the children of the node children = safe_call(self._zk, 'get_children', self._locknode) children.sort(key=lambda val: val[val.rfind('-') + 1:]) if keyname not in children: return False acquired = self._has_lock(keyname, children)[0] return bool(acquired)
def _acquire_lock(self, node_name, timeout=None, revoke=False): """Acquire a lock Internal function used by read/write lock :param node_name: Name of the node to use for the lock :type node_name: str :param timeout: How long to wait to acquire the lock, set to 0 to get non-blocking behavior. :type timeout: int :param revoke: Whether prior locks should be revoked. Can be set to True to request and wait for prior locks to release their lock, or :obj:`IMMEDIATE` to destroy the blocking read/write locks and attempt to acquire a write lock. :type revoke: bool or :obj:``IMMEDIATE`` :returns: True if the lock was acquired, False otherwise :rtype: bool """ # First clear out any prior revocation warnings self._revoked = [] # Create a lock node self._candidate_path = znode = safe_create_ephemeral_sequence( self._zk, self._locknode + node_name, "0", [ZOO_OPEN_ACL_UNSAFE]) @threaded def revoke_watcher(handle, type, state, path): # This method must be in closure scope to ensure that # it can append to the thread it is called from # to indicate if this particular thread's lock was # revoked or removed if type == zookeeper.CHANGED_EVENT: data = safe_call(self._zk, 'get', path, revoke_watcher)[0] if data == 'unlock': self._revoked.append(True) elif type == zookeeper.DELETED_EVENT or \ state == zookeeper.EXPIRED_SESSION_STATE: # Trigger if node was deleted self._revoked.append(True) data = safe_call(self._zk, 'get', znode, revoke_watcher)[0] if data == 'unlock': self._revoked.append(True) keyname = znode[znode.rfind('/') + 1:] acquired = False cv = threading.Event() def lock_watcher(handle, type, state, path): cv.set() lock_start = time.time() first_run = True while not acquired: cv.clear() # Have we been at this longer than the timeout? if not first_run: if timeout is not None and time.time() - lock_start > timeout: try: safe_call(self._zk, 'delete', znode) except zookeeper.NoNodeException: pass return False first_run = False # Get all the children of the node children = safe_call(self._zk, 'get_children', self._locknode) children.sort(key=lambda val: val[val.rfind('-') + 1:]) if len(children) == 0 or not keyname in children: # Disconnects or other errors can cause this self._candidate_path = znode = safe_create_ephemeral_sequence( self._zk, self._locknode + node_name, "0", [ZOO_OPEN_ACL_UNSAFE]) keyname = znode[znode.rfind('/') + 1:] data = safe_call(self._zk, 'get', znode, revoke_watcher)[0] if data == 'unlock': self._revoked.append(True) continue acquired, blocking_nodes = self._has_lock(keyname, children) if acquired: break if revoke == IMMEDIATE: # Remove all prior nodes for node in blocking_nodes: try: safe_call(self._zk, 'delete', self._locknode + '/' + node) except zookeeper.NoNodeException: pass continue # Now try again elif revoke: # Ask all prior blocking nodes to release for node in blocking_nodes: try: safe_call(self._zk, 'set', self._locknode + '/' + node, "unlock") except zookeeper.NoNodeException: pass prior_blocking_node = self._locknode + '/' + blocking_nodes[-1] exists = safe_call(self._zk, 'exists', prior_blocking_node, lock_watcher) if not exists: # The node disappeared? Rinse and repeat. continue # Wait for a notification from get_children, no longer # than the timeout wait_for = None if timeout is not None: time_spent = time.time() - lock_start wait_for = timeout - time_spent cv.wait(wait_for) return True