예제 #1
0
 def testNotifyWhenPoolEmpties(self):
     """
     After all the deferreds in a pool fire, it should call all notify
     callbacks.
     """
     pool = DeferredPool()
     d1 = Deferred()
     d2 = Deferred()
     d3 = Deferred()
     pool.add(d1)
     pool.add(d2)
     pool.add(d3)
     # There must be 3 deferreds underway.
     self.assertEqual(pool.status(), (3, 0))
     wait1 = pool.notifyWhenEmpty()
     wait2 = pool.notifyWhenEmpty()
     # There must be 3 deferreds underway and 2 waiting.
     self.assertEqual(pool.status(), (3, 2))
     d1.callback(None)
     d2.callback(None)
     # There must be 1 deferreds underway and 2 waiting.
     self.assertEqual(pool.status(), (1, 2))
     # The waiters must not have fired yet.
     self.assertEqual(wait1.called, False)
     self.assertEqual(wait2.called, False)
     d3.callback(None)
     # Both waiters must have fired (with None) & the pool must be empty.
     self.assertEqual(wait1.called, True)
     self.assertEqual(wait2.called, True)
     self.assertEqual(pool.status(), (0, 0))
     self.assertEqual(wait1.result, None)
     self.assertEqual(wait2.result, None)
예제 #2
0
 def testEmptyPoolStatus(self):
     """
     A newly created (empty) pool should have no deferreds in progress
     and no deferreds waiting for the pool to drain.
     """
     pool = DeferredPool()
     self.assertEqual(pool.status(), (0, 0))
예제 #3
0
 def testPoolDeferredCount(self):
     """
     The pool must correctly report how many deferreds it has underway.
     """
     pool = DeferredPool()
     pool.add(Deferred())
     pool.add(Deferred())
     self.assertEqual(pool.status(), (2, 0))
예제 #4
0
 def testPoolWaitingCount(self):
     """
     The pool must correctly report how many deferreds are waiting
     for it to drain.
     """
     pool = DeferredPool()
     pool.notifyWhenEmpty(testImmediately=False)
     pool.notifyWhenEmpty(testImmediately=False)
     self.assertEqual(pool.status(), (0, 2))
예제 #5
0
 def testEmptyPoolWithTestImmediatelyFalse(self):
     """
     If the pool is empty and C{testImmediately} is C{False} when
     calling L{notifyWhenEmpty}, the returned deferred must not have
     already fired and the pool should contain one waiting deferred.
     """
     pool = DeferredPool()
     d = pool.notifyWhenEmpty(testImmediately=False)
     self.assertEqual(d.called, False)
     self.assertEqual(pool.status(), (0, 1))
예제 #6
0
 def testEmptyPoolWithTestImmediatelyTrue(self):
     """
     If the pool is empty and C{testImmediately} is C{True} when
     calling L{notifyWhenEmpty}, an already fired (with C{None} result)
     deferred must be returned and the pool should be empty.
     """
     pool = DeferredPool()
     d = pool.notifyWhenEmpty(testImmediately=True)
     self.assertEqual(d.called, True)
     self.assertEqual(d.result, None)
     self.assertEqual(pool.status(), (0, 0))
예제 #7
0
 def testCallbackedDeferredFiresWithTheRightResult(self):
     """
     The pool must correctly pass the original deferred callback result
     through any callbacks it might have added.
     """
     pool = DeferredPool()
     d = Deferred()
     pool.add(d)
     pool.notifyWhenEmpty()
     expectedValue = object()
     d.callback(expectedValue)
     self.assertIdentical(d.result, expectedValue)
예제 #8
0
 def testNonEmptyPoolWithTestImmediatelyTrue(self):
     """
     If the pool is not empty and C{testImmediately} is C{True} when
     calling L{notifyWhenEmpty}, the returned deferred must not have
     already fired and the pool should contain one underway deferred
     and one waiting.
     """
     pool = DeferredPool()
     pool.add(Deferred())
     d = pool.notifyWhenEmpty(testImmediately=True)
     self.assertEqual(d.called, False)
     self.assertEqual(pool.status(), (1, 1))
예제 #9
0
 def testErrbackedDeferredFiresWithTheRightResult(self):
     """
     The pool must correctly pass the original deferred errback result
     through any callbacks it might have added.
     """
     expectedValue = Exception()
     pool = DeferredPool()
     d = Deferred()
     pool.add(d)
     pool.notifyWhenEmpty()
     d.errback(expectedValue)
     self.assertIdentical(d.result.value, expectedValue)
     return self.assertFailure(d, Exception)
예제 #10
0
파일: cache.py 프로젝트: jkakar/Tickery
 def __init__(self, cacheDir, restoreAddQueue, queueWidth, endpoint):
     self.cacheDir = cacheDir
     self.restoreAddQueue = restoreAddQueue
     self.queueWidth = queueWidth
     self.endpoint = endpoint
     # oauthTokenDict is a volatile cache.
     self.oauthTokenDict = {}
     self.extraTwitterTagsPool = DeferredPool()
예제 #11
0
파일: rdq.py 프로젝트: mwilliams3/txrdq
 def __init__(self, func, width=0, size=None, backlog=None):
     self._func = func
     self.stopped = self.paused = False
     self._queue = DeferredPriorityQueue(size, backlog)
     self._pool = DeferredPool()
     self._coop = task.Cooperator()
     self._currentWidth = 0
     self.pendingStops = 0
     self._underway = set()
     self.width = width
예제 #12
0
 def testPoolStatusAfterDeferredIsCallbacked(self):
     """
     The pool must correctly report how many deferreds are underway
     and how many are waiting for it to drain after one of its underway
     deferreds is callbacked.
     """
     pool = DeferredPool()
     d = Deferred()
     pool.add(d)
     pool.add(Deferred())
     self.assertEqual(pool.status(), (2, 0))
     d.callback(None)
     self.assertEqual(pool.status(), (1, 0))
예제 #13
0
 def testPoolStatus(self):
     """
     The pool must correctly report how many deferreds are underway
     and how many are waiting for it to drain.
     """
     pool = DeferredPool()
     pool.add(Deferred())
     pool.add(Deferred())
     pool.notifyWhenEmpty(testImmediately=False)
     self.assertEqual(pool.status(), (2, 1))
예제 #14
0
 def testPoolStatusAfterDeferredIsErrbacked(self):
     """
     The pool must correctly report how many deferreds are underway
     and how many are waiting for it to drain after one of its underway
     deferreds is errbacked.
     """
     pool = DeferredPool()
     d = Deferred()
     d.addErrback(lambda _: True)
     pool.add(d)
     pool.add(Deferred())
     self.assertEqual(pool.status(), (2, 0))
     d.errback(Exception())
     self.assertEqual(pool.status(), (1, 0))
예제 #15
0
파일: cache.py 프로젝트: jkakar/Tickery
class TickeryCache(service.Service):
    def __init__(self, cacheDir, restoreAddQueue, queueWidth, endpoint):
        self.cacheDir = cacheDir
        self.restoreAddQueue = restoreAddQueue
        self.queueWidth = queueWidth
        self.endpoint = endpoint
        # oauthTokenDict is a volatile cache.
        self.oauthTokenDict = {}
        self.extraTwitterTagsPool = DeferredPool()

    def startService(self):
        self.loadAllCaches()

    def loadAllCaches(self):
        service.Service.startService(self)

        cacheFile = functools.partial(os.path.join, self.cacheDir)

        self.userCache = TwitterUserCache()
        self.userCache.load(cacheFile("users"))

        self.oidUidScreennameCache = OidUidScreennameCache(self.endpoint)
        self.oidUidScreennameCache.load(cacheFile("oidUidScreenname"))

        self.queryCache = QueryCache()
        self.queryCache.load(cacheFile("queries"))

        self.cookieCache = CookieCache()
        self.cookieCache.load(cacheFile("cookies"))

        self.adderCache = AdderCache(self, self.queueWidth, self.endpoint)
        self.adderCache.load(cacheFile("adder"))

        self.friendsIdCache = FriendsCache()
        self.friendsIdCache.load(cacheFile("friends"))

    @defer.inlineCallbacks
    def stopService(self):
        yield self.adderCache.close()
        yield self.extraTwitterTagsPool.notifyWhenEmpty()
        self.userCache.close()
        self.oidUidScreennameCache.close()
        self.queryCache.close()
        self.cookieCache.close()
        self.friendsIdCache.close()
        service.Service.stopService(self)
예제 #16
0
 def testPoolEmpties(self):
     """
     If all the deferreds in a pool fire, its underway list should be empty.
     """
     pool = DeferredPool()
     d1 = Deferred()
     d2 = Deferred()
     d3 = Deferred()
     pool.add(d1)
     pool.add(d2)
     pool.add(d3)
     self.assertEqual(pool.status(), (3, 0))
     d1.callback(None)
     d2.callback(None)
     d3.callback(None)
     self.assertEqual(pool.status(), (0, 0))
예제 #17
0
파일: rdq.py 프로젝트: mwilliams3/txrdq
class ResizableDispatchQueue(object):
    """
    @ivar func: The one-argument function that will be called to process
    each job added to the queue.

    @ivar width: The 'width' of the queue. This is the maximum number of
    jobs that the queue will have in progress at any time. Set to 0
    to pause queue processing.

    @ivar size: The maximum number of objects to allow into the queue at a
    time. See the documentation for twisted.internet.defer.DeferredQueue

    @ivar backlog: The maximum number of L{Deferred} gets to allow at one
    time.  See the documentation for twisted.internet.defer.DeferredQueue.

    @raise ValueError: if width is not an integer or is less than zero.
    """

    _NOOP = object()

    def __init__(self, func, width=0, size=None, backlog=None):
        self._func = func
        self.stopped = self.paused = False
        self._queue = DeferredPriorityQueue(size, backlog)
        self._pool = DeferredPool()
        self._coop = task.Cooperator()
        self._currentWidth = 0
        self.pendingStops = 0
        self._underway = set()
        self.width = width

    def put(self, jobarg, priority=0):
        """
        Add a job to the queue.

        @param jobarg: The argument that will be passed to self._func when
        this job is launched.

        @param priority: The priority for the job. Lower means more
        important.  Default is zero.

        @raise QueueStopped: if the queue has been stopped.

        @return: a C{Deferred} that will eventually fire with the result of
        calling self._func on jobarg.
        """
        if self.stopped:
            raise QueueStopped()
        else:
            job = Job(jobarg, priority)
            d = job.watch().addBoth(self._jobDone, job)
            self._queue.put(job, priority)
            return d

    def _jobDone(self, result, job):
        """
        A job has finished one way or another. 'result' is either a
        successful (C{Job}) result or a failure (containing a C{Job} that
        failed or was cancelled). The job argument contains the original
        job in any case.

        Remove a finished job from our queue or (if it's no longer there)
        from the set of underway jobs.

        @param result: the result of the job processing.

        @param job: the job that has just finished.

        @return: the result we are passed.
        """
        try:
            self._queue.delete(job)
        except KeyError:
            self._underway.discard(job)
        return result

    def pending(self):
        """
        Get the pending jobs.

        @return: A C{list} of the L{Job}s that are pending (i.e., which
        have not yet begun to be processed).
        """
        # Turn the generator from our pending queue into a list, to make
        # sure we're returning a snapshot of the queue at this moment.  The
        # list can be altered by our caller without affecting us.
        return list(self._queue.asGenerator())

    def underway(self):
        """
        Get the currently underway jobs. These can have cancel called on
        them and can be reprioritized.

        @return: A (copy of) the set of jobs that are underway.
        """
        # We return a copy in case our caller modifies the return value.
        return set(self._underway)

    def clearQueue(self, cancelPending=True):
        """
        Clear the pending list of jobs.

        @param cancelPending: if True, call 'cancel' on the C{Deferred}s
        for the pending jobs.
        """
        if cancelPending:
            jobs = list(self._queue.asGenerator())
            for job in jobs:
                job.cancel()
        self._queue.clear()

    def size(self):
        """
        Provide information about our size.

        @return: A two-tuple with the number of jobs that are underway and
        the number that are pending.
        """
        return (len(self._underway), len(self._queue))

    def _drain(self):
        """
        For a queue that is either stopped or paused, return a deferred
        that fires when the currently underway jobs in the queue have all
        finished.

        @raises RuntimeError: if the queue is not paused or stopped.

        @return: a C{Deferred} that fires when no jobs are underway.
        """
        # The queue must be paused or stopped in order for us to sanely
        # wait for it to drain to zero.
        if not (self.paused or self.stopped):
            raise RuntimeError('Queue is not paused or stopped.')

        # Flush idle job dispatcher (Deferreds) by giving each a no-op job.
        while self._queue.waiting:
            # We pass an arbitrary priority of zero. We know the queue is
            # empty because self._queue.waiting is not empty, so task
            # priority is irrelevant.
            self._queue.put(self._NOOP, 0)

        return self._pool.notifyWhenEmpty()

    def stop(self, cancelUnderway=False):
        """
        Permanently stop the queue. Any subsequent attempt to put a job
        onto the queue will result in an error. The default behavior is to
        wait for underway jobs to finish, but see the docs below on
        cancelUnderway.

        @param cancelUnderway: if True: 1) call cancel on the deferreds for
        all the currently underway jobs, and 2) add those underway jobs to
        the list of pending jobs returned by the C{Deferred} we return.

        @return: a C{Deferred} that fires with the list of pending jobs.
        """
        self.stopped = True
        self.width = 0

        log.msg('Stop called. Underway in queue %r' % self._underway)

        if cancelUnderway:
            underwayJobs = list(self._underway)
            for job in underwayJobs:
                job.cancel()
        else:
            underwayJobs = []
        log.msg('stop called. underwayJobs is %r' % underwayJobs)
        d = self._drain()
        d.addCallback(lambda _: underwayJobs + self.pending())
        return d

    def reprioritize(self, job, priority):
        """
        Set the priority of a pending job to priority.

        @raise: C{RuntimeError} if the job is not PENDING.

        @raise: C{KeyError} if the job does not exist.

        @param job: The job whose priority should be altered.

        @param priority: The new priority. Lower is more important.
        """
        if job.state != Job.PENDING:
            raise RuntimeError('Cannot delete job that is not pending.')
        else:
            self._queue.reprioritize(job, priority)
            job.priority = priority

    def pause(self):
        self._pausedWidth = self.width
        self.width = 0
        self.paused = True
        return self._drain()

    def resume(self, width=None):
        if self.stopped:
            raise QueueStopped()
        if width is None:
            width = self._pausedWidth
        else:
            width = int(width)
            if width < 0:
                raise ValueError('Queue width cannot be negative.')
        self.paused = False
        self.width = width

    def next(self):
        """
        Return the next job to be processed, unless we are currenly
        narrowing the queue, in which case we raise C{StopIteration}.

        @raise StopIteration: if the queue is in the process of being
        narrowed.

        @return: a C{Deferred} that will fire when a job is added to the
        queue.
        """
        if self.pendingStops:
            self.pendingStops -= 1
            self._currentWidth -= 1
            raise StopIteration
        else:
            return self._queue.get().addCallback(self._launchJob)

    def _launchJob(self, job):
        """
        Launch a queued job.

        @param job: a L{Job} to get underway.  If the job is a no-op, do
        nothing.

        @return: If the job is a no-op, return C{None}. Else return a
        C{Deferred} that will fire when the job has completed.
        """
        if job is not self._NOOP:
            self._underway.add(job)
            job.launch(self._func)
            # We don't care about the result of the job here, we just want
            # to return a deferred that fires when the job is done. Ignore
            # any failure in the deferred returned by C{job.watch} so as
            # not to trigger error messages elsewhere (e.g., from trial)
            # about unhandled errors. (The processing of the job result is
            # handled in C{self._jobDone}, which is called by the deferred
            # returned by C{self.put}.)
            return job.watch().addErrback(lambda err: None)

    def getWidth(self):
        """
        Return the current width of the queue.

        Note that this may be greater or less than the number of jobs
        currently underway. For example, the width could be 10 but we may
        have only 7 jobs underway (due to their being no more pending jobs
        to launch). If the width is then reduced to 4, this method will
        report width as 4 even though there are still 7 jobs in progress.

        @return: the current width of the queue.
        """
        if self.paused:
            return self._pausedWidth
        else:
            return self._currentWidth - self.pendingStops

    def setWidth(self, width):
        """
        Set the width of the queue. If the queue is paused, we simply
        remember what the width should be once queue processing is resumed.
        If we're not paused, then the interesting cases are either making
        the queue wider or narrower. To make ourselves wider, we pass self
        to our coiterator the appropriate number of times. To make
        ourselves narrower, we arrange for self.next to raise StopIteration
        the appropriate number of times.

        @param width: the new width (a non-negative integer) for the queue.

        @raises ValueError: if width is not an integer or is less than zero.
        """
        width = int(width)
        if width < 0:
            raise ValueError('Queue width cannot be negative.')
        if self.paused:
            self._pausedWidth = width
        else:
            increment = width - self.width
            if increment > 0:
                # Make ourselves wider.
                #
                # The actual increment is the requested increment minus the
                # any pending stops (i.e., narrows) we have scheduled.
                actualIncrement = increment - self.pendingStops
                if actualIncrement >= 0:
                    self.pendingStops = 0
                    for i in xrange(actualIncrement):
                        self._pool.add(self._coop.coiterate(self))
                    self._currentWidth += actualIncrement
                else:
                    self.pendingStops -= increment
            elif increment < 0:
                # Make ourselves narrower.
                increment = -increment
                self.pendingStops += increment
                # If there are already waiters on our queue, give up to
                # 'increment' of them a no-op. That ensures that those
                # Deferreds that are already awaiting job args will not
                # process the next values put to the RDQ, they will no-op
                # instead. If there aren't waiters it's no problem, the
                # reduction in queue width will be handled by pending
                # stops.
                #
                # I know, this is too complicated :-(
                #
                # The priority on the put below isn't important. The fact
                # that there are waiters means the DeferredPriorityQueue is
                # empty. So the no-op will go straight out to a waiter no
                # matter what priority it has.
                for _ in range(increment):
                    if self._queue.waiting:
                        self._queue.put(self._NOOP, 0)
                    else:
                        break

    width = property(getWidth, setWidth)