Example #1
0
 def test_lock_reuse(self):
     """
     A lock instance may be reused after an acquire/release cycle.
     """
     client = yield self.open_client()
     path = yield client.create("/lock-test")
     lock = Lock(path, client)
     yield lock.acquire()
     self.assertTrue(lock.acquired)
     yield lock.release()
     self.assertFalse(lock.acquired)
     yield lock.acquire()
     self.assertTrue(lock.acquired)
     yield lock.release()
     self.assertFalse(lock.acquired)
Example #2
0
 def test_error_when_releasing_unacquired(self):
     """
     If an attempt is made to release a lock, that not currently being held,
     than a C{LockError} exception is raised.
     """
     client = yield self.open_client()
     lock_dir = yield client.create("/lock-multi-test")
     lock = Lock(lock_dir, client)
     self.failUnlessFailure(lock.release(), LockError)
Example #3
0
 def test_acquire_release(self):
     """
     A lock can be acquired and released.
     """
     client = yield self.open_client()
     path = yield client.create("/lock-test")
     lock = Lock(path, client)
     yield lock.acquire()
     self.assertEqual(lock.acquired, True)
     released = yield lock.release()
     self.assertEqual(released, True)
Example #4
0
    def test_multiple_acquiring_clients(self):
        """
        Multiple clients can compete for the lock, only one client's Lock
        instance may hold the lock at any given moment.
        """
        client = yield self.open_client()
        client2 = yield self.open_client()
        lock_dir = yield client.create("/lock-multi-test")

        lock = Lock(lock_dir, client)
        lock2 = Lock(lock_dir, client2)

        yield lock.acquire()
        self.assertTrue(lock.acquired)
        lock2_acquire = lock2.acquire()
        yield lock.release()
        yield lock2_acquire
        self.assertTrue(lock2.acquired)
        self.assertFalse(lock.acquired)
Example #5
0
class SerializedQueue(Queue):
    """
    A serialized queue ensures even with multiple consumers items are retrieved
    and processed in the order they where placed in the queue.

    This implementation aggregates a reliable queue, with a lock to provide
    for serialized consumer access. The lock is released only when a queue item
    has been processed.
    """

    def __init__(self, path, client, acl=None, persistent=False):
        super(SerializedQueue, self).__init__(path, client, acl, persistent)
        self._lock = Lock("%s/%s" % (self.path, "_lock"), client)

    def _item_processed_callback(self, result_code, item_path):
        return self._lock.release()

    def _filter_children(self, children, suffix="-processing"):
        """
        Filter the lock from consideration as an item to be processed.
        """
        children.sort()
        for name in list(children):
            if name.startswith('_'):
                children.remove(name)

    def _on_lock_directory_does_not_exist(self, failure):
        """
        If the lock directory does not exist, go ahead and create it and
        attempt to acquire the lock.
        """
        failure.trap(zookeeper.NoNodeException)
        d = self._client.create(self._lock.path)
        d.addBoth(self._on_lock_created_or_exists)
        return d

    def _on_lock_created_or_exists(self, failure):
        """
        The lock node creation will either result in success or node exists
        error, if a concurrent client created the node first. In either case
        we proceed with attempting to acquire the lock.
        """
        if isinstance(failure, Failure):
            failure.trap(zookeeper.NodeExistsException)
        d = self._lock.acquire()
        return d

    def _on_lock_acquired(self, lock):
        """
        After the exclusive queue lock is acquired, we proceed with an attempt
        to fetch an item from the queue.
        """
        d = super(SerializedQueue, self).get()
        return d

    def get(self):
        """
        Get and remove an item from the queue. If no item is available
        at the moment, a deferred is return that will fire when an item
        is available.
        """
        d = self._lock.acquire()

        d.addErrback(self._on_lock_directory_does_not_exist)
        d.addCallback(self._on_lock_acquired)
        return d

    def _get_item(self, children, request):

        def fetch_node(name):
            path = "/".join((self._path, name))
            d = self._client.get(path)
            d.addCallback(on_node_retrieved, path)
            d.addErrback(on_reservation_failed)
            return d

        def on_node_retrieved((data, stat), path):
            request.processing_children = False
            request.callback(
                QueueItem(
                    path, data, self._client, self._item_processed_callback))

        def on_reservation_failed(failure=None):
            """If we can't get the node or reserve, continue processing
            the children."""
            if failure and not failure.check(
                zookeeper.NodeExistsException, zookeeper.NoNodeException):
                request.processing_children = True
                request.errback(failure)
                return

            if children:
                name = children.pop(0)
                return fetch_node(name)

            # If a watch fired while processing children, process it
            # after the children list is exhausted.
            request.processing_children = False
            if request.refetch_children:
                request.refetch_children = False
                return self._get(request)

        self._filter_children(children)

        if not children:
            return on_reservation_failed()

        name = children.pop(0)
        return fetch_node(name)