Example #1
0
    def test_rerun_after_depletion_doesnt_delete_too_much(self):
        """Ensure MessageIterator works when used manually."""
        from furious.batcher import MessageIterator

        payload = '["test"]'
        task = Mock(payload=payload, tag='tag')

        iterator = MessageIterator('tag', 'qn', 1, auto_delete=False)

        with patch.object(iterator, 'queue') as queue:
            queue.lease_tasks_by_tag.return_value = [task]

            results = [payload for payload in iterator]
            self.assertEqual(results, [payload])

            # This new work should never be leased, but simulates new pending
            # work.
            task_1 = Mock(payload='["task_1"]', tag='tag')
            queue.lease_tasks_by_tag.return_value = [task_1]

            # Iterating again should return the "originally leased" work, not
            # new work.
            results = [payload for payload in iterator]
            self.assertEqual(results, [payload])

            # Lease should only have been called a single time.
            queue.lease_tasks_by_tag.assert_called_once_with(
                60, 1, tag='tag')

            # The delete call should delete only the original work.
            iterator.delete_messages()
            queue.delete_tasks.assert_called_once_with([task])
Example #2
0
    def test_rerun_after_depletion_doesnt_delete_too_much(self):
        """Ensure MessageIterator works when used manually."""
        from furious.batcher import MessageIterator

        payload = '["test"]'
        task = Mock(payload=payload, tag='tag')

        iterator = MessageIterator('tag', 'qn', 1, auto_delete=False)

        with patch.object(iterator, 'queue') as queue:
            queue.lease_tasks_by_tag.return_value = [task]

            results = [payload for payload in iterator]
            self.assertEqual(results, [payload])

            # This new work should never be leased, but simulates new pending
            # work.
            task_1 = Mock(payload='["task_1"]', tag='tag')
            queue.lease_tasks_by_tag.return_value = [task_1]

            # Iterating again should return the "originally leased" work, not
            # new work.
            results = [payload for payload in iterator]
            self.assertEqual(results, [payload])

            # Lease should only have been called a single time.
            queue.lease_tasks_by_tag.assert_called_once_with(60,
                                                             1,
                                                             tag='tag',
                                                             deadline=10)

            # The delete call should delete only the original work.
            iterator.delete_messages()
            queue.delete_tasks.assert_called_once_with([task])
Example #3
0
    def test_raise_stopiteration_if_no_messages(self):
        """Ensure MessageIterator raises StopIteration if no messages."""
        from furious.batcher import MessageIterator

        iterator = MessageIterator('tag', 'qn', 1)
        with patch.object(iterator, 'queue') as queue:
            queue.lease_tasks_by_tag.return_value = []

        self.assertRaises(StopIteration, iterator.next)
Example #4
0
    def test_time_check(self, time):
        """Ensure that a DeadlineExceededError is thrown when the lease takes
        over (deadline-0.1) secs.
        """
        from google.appengine.runtime import apiproxy_errors
        from furious.batcher import MessageIterator

        time.side_effect = [0.0, 9.9]
        message_iterator = MessageIterator('tag', 'qn', 1)

        with patch.object(message_iterator, 'queue') as queue:
            queue.lease_tasks_by_tag.return_value = []

            self.assertRaises(apiproxy_errors.DeadlineExceededError, iter,
                              message_iterator)
Example #5
0
    def test_iterates(self):
        """Ensure MessageIterator instances iterate in loop."""
        from furious.batcher import MessageIterator

        payload = '["test"]'
        task = Mock(payload=payload, tag='tag')
        task1 = Mock(payload=payload, tag='tag')
        task2 = Mock(payload=payload, tag='tag')

        iterator = MessageIterator('tag', 'qn', 1)

        with patch.object(iterator, 'queue') as queue:
            queue.lease_tasks_by_tag.return_value = [task, task1, task2]

            results = [payload for payload in iterator]

        self.assertEqual(results, [payload, payload, payload])
Example #6
0
    def test_rerun_after_depletion_calls_once(self):
        """Ensure MessageIterator works when used manually."""
        from furious.batcher import MessageIterator

        payload = '["test"]'
        task = Mock(payload=payload, tag='tag')

        iterator = MessageIterator('tag', 'qn', 1)

        with patch.object(iterator, 'queue') as queue:
            queue.lease_tasks_by_tag.return_value = [task]

            results = [payload for payload in iterator]
            self.assertEqual(results, [payload])

            results = [payload for payload in iterator]

        queue.lease_tasks_by_tag.assert_called_once_with(60, 1, tag='tag')
Example #7
0
    def test_calls_lease_exactly_once(self):
        """Ensure MessageIterator calls lease only once."""
        from furious.batcher import MessageIterator

        payload = '["test"]'
        task = Mock(payload=payload, tag='tag')

        message_iterator = MessageIterator('tag', 'qn', 1)

        with patch.object(message_iterator, 'queue') as queue:
            queue.lease_tasks_by_tag.return_value = [task]

            iterator = iter(message_iterator)
            iterator.next()

            self.assertRaises(StopIteration, iterator.next)
            self.assertRaises(StopIteration, iterator.next)

        queue.lease_tasks_by_tag.assert_called_once_with(60, 1, tag='tag')
Example #8
0
    def test_custom_deadline(self):
        """Ensure that a custom deadline gets passed to lease_tasks."""
        from furious.batcher import MessageIterator

        payload = '["test"]'
        task = Mock(payload=payload, tag='tag')

        message_iterator = MessageIterator('tag', 'qn', 1, deadline=2)

        with patch.object(message_iterator, 'queue') as queue:
            queue.lease_tasks_by_tag.return_value = [task]

            iterator = iter(message_iterator)
            iterator.next()

            self.assertRaises(StopIteration, iterator.next)

        queue.lease_tasks_by_tag.assert_called_once_with(60,
                                                         1,
                                                         tag='tag',
                                                         deadline=2)
Example #9
0
def process_messages(tag, retries=0):
    """Processes the messages pulled fromm a queue based off the tag passed in.
    Will insert another processor if any work was processed or the retry count
    is under the max retry count. Will update a aggregated stats object with
    the data in the payload of the messages processed.

    :param tag: :class: `str` Tag to query the queue on
    :param retry: :class: `int` Number of retries the job has processed
    """
    from furious.batcher import bump_batch
    from furious.batcher import MESSAGE_DEFAULT_QUEUE
    from furious.batcher import MessageIterator
    from furious.batcher import MessageProcessor

    from google.appengine.api import memcache

    # since we don't have a flag for checking complete we'll re-insert a
    # processor task with a retry count to catch any work that may still be
    # filtering in. If we've hit our max retry count we just bail out and
    # consider the job complete.
    if retries > 5:
        logging.info("Process messages hit max retry and is exiting")
        return

    # create a message iteragor for the tag in batches of 500
    message_iterator = MessageIterator(tag, MESSAGE_DEFAULT_QUEUE, 500)

    client = memcache.Client()

    # get the stats object from cache
    stats = client.gets(tag)

    # json decode it if it exists otherwise get the default state.
    stats = json.loads(stats) if stats else get_default_stats()

    work_processed = False

    # loop through the messages pulled from the queue.
    for message in message_iterator:
        work_processed = True

        value = int(message.get("value", 0))
        color = message.get("color").lower()

        # update the total stats with the value pulled
        set_stats(stats["totals"], value)

        # update the specific color status via the value pulled
        set_stats(stats["colors"][color], value)

    # insert the stats back into cache
    json_stats = json.dumps(stats)

    # try and do an add first to see if it's new. We can't trush get due to
    # a race condition.
    if not client.add(tag, json_stats):
        # if we couldn't add than lets do a compare and set to safely
        # update the stats
        if not client.cas(tag, json_stats):
            raise Exception("Transaction Collision.")

    # bump the process batch id
    bump_batch(tag)

    if work_processed:
        # reset the retries as we've processed work
        retries = 0
    else:
        # no work was processed so increment the retries
        retries += 1

    # insert another processor
    processor = MessageProcessor(target=process_messages,
                                 args=("colors", ),
                                 kwargs={'retries': retries},
                                 tag="colors")

    processor.start()