示例#1
0
class Queue(object):
    """
     SIMPLE MULTI-THREADED QUEUE

     (multiprocessing.Queue REQUIRES SERIALIZATION, WHICH
     IS DIFFICULT TO USE JUST BETWEEN THREADS)
    """
    def __init__(self,
                 name,
                 max=None,
                 silent=False,
                 unique=False,
                 allow_add_after_close=False):
        """
        max - LIMIT THE NUMBER IN THE QUEUE, IF TOO MANY add() AND extend() WILL BLOCK
        silent - COMPLAIN IF THE READERS ARE TOO SLOW
        unique - SET True IF YOU WANT ONLY ONE INSTANCE IN THE QUEUE AT A TIME
        """
        self.name = name
        self.max = coalesce(max, 2**10)
        self.silent = silent
        self.allow_add_after_close = allow_add_after_close
        self.unique = unique
        self.closed = Signal(
            "stop adding signal for " +
            name)  # INDICATE THE PRODUCER IS DONE GENERATING ITEMS TO QUEUE
        self.lock = Lock("lock for queue " + name)
        self.queue = deque()

    def __iter__(self):
        try:
            while True:
                value = self.pop()
                if value is THREAD_STOP:
                    break
                if value is not None:
                    yield value
        except Exception as e:
            Log.warning("Tell me about what happened here", e)

    def add(self, value, timeout=None, force=False):
        """
        :param value:  ADDED THE THE QUEUE
        :param timeout:  HOW LONG TO WAIT FOR QUEUE TO NOT BE FULL
        :param force:  ADD TO QUEUE, EVEN IF FULL (USE ONLY WHEN CONSUMER IS RETURNING WORK TO THE QUEUE)
        :return: self
        """
        with self.lock:
            if value is THREAD_STOP:
                # INSIDE THE lock SO THAT EXITING WILL RELEASE wait()
                self.queue.append(value)
                self.closed.go()
                return

            if not force:
                self._wait_for_queue_space(timeout=timeout)
            if self.closed and not self.allow_add_after_close:
                Log.error("Do not add to closed queue")
            if self.unique:
                if value not in self.queue:
                    self.queue.append(value)
            else:
                self.queue.append(value)
        return self

    def push(self, value):
        """
        SNEAK value TO FRONT OF THE QUEUE
        """
        if self.closed and not self.allow_add_after_close:
            Log.error("Do not push to closed queue")

        with self.lock:
            self._wait_for_queue_space()
            if not self.closed:
                self.queue.appendleft(value)
        return self

    def push_all(self, values):
        """
        SNEAK values TO FRONT OF THE QUEUE
        """
        if self.closed and not self.allow_add_after_close:
            Log.error("Do not push to closed queue")

        with self.lock:
            self._wait_for_queue_space()
            if not self.closed:
                self.queue.extendleft(values)
        return self

    def pop_message(self, till=None):
        """
        RETURN TUPLE (message, payload) CALLER IS RESPONSIBLE FOR CALLING message.delete() WHEN DONE
        DUMMY IMPLEMENTATION FOR DEBUGGING
        """

        if till is not None and not isinstance(till, Signal):
            Log.error("Expecting a signal")
        return Null, self.pop(till=till)

    def extend(self, values):
        if self.closed and not self.allow_add_after_close:
            Log.error("Do not push to closed queue")

        with self.lock:
            # ONCE THE queue IS BELOW LIMIT, ALLOW ADDING MORE
            self._wait_for_queue_space()
            if not self.closed:
                if self.unique:
                    for v in values:
                        if v is THREAD_STOP:
                            self.closed.go()
                            continue
                        if v not in self.queue:
                            self.queue.append(v)
                else:
                    for v in values:
                        if v is THREAD_STOP:
                            self.closed.go()
                            continue
                        self.queue.append(v)
        return self

    def _wait_for_queue_space(self, timeout=None):
        """
        EXPECT THE self.lock TO BE HAD, WAITS FOR self.queue TO HAVE A LITTLE SPACE

        :param timeout:  IN SECONDS
        """
        wait_time = 5

        (DEBUG and len(self.queue) > 1 * 1000 * 1000
         ) and Log.warning("Queue {{name}} has over a million items")

        start = time()
        stop_waiting = Till(till=start + coalesce(timeout, DEFAULT_WAIT_TIME))

        while not self.closed and len(self.queue) >= self.max:
            if stop_waiting:
                Log.error(THREAD_TIMEOUT)

            if self.silent:
                self.lock.wait(stop_waiting)
            else:
                self.lock.wait(Till(seconds=wait_time))
                if not stop_waiting and len(self.queue) >= self.max:
                    now = time()
                    Log.alert(
                        "Queue with name {{name|quote}} is full with ({{num}} items), thread(s) have been waiting {{wait_time}} sec",
                        name=self.name,
                        num=len(self.queue),
                        wait_time=now - start)

    def __len__(self):
        with self.lock:
            return len(self.queue)

    def __nonzero__(self):
        with self.lock:
            return any(r != THREAD_STOP for r in self.queue)

    def pop(self, till=None):
        """
        WAIT FOR NEXT ITEM ON THE QUEUE
        RETURN THREAD_STOP IF QUEUE IS CLOSED
        RETURN None IF till IS REACHED AND QUEUE IS STILL EMPTY

        :param till:  A `Signal` to stop waiting and return None
        :return:  A value, or a THREAD_STOP or None
        """
        if till is not None and not isinstance(till, Signal):
            Log.error("expecting a signal")

        with self.lock:
            while True:
                if self.queue:
                    return self.queue.popleft()
                if self.closed:
                    break
                if not self.lock.wait(till=self.closed | till):
                    if self.closed:
                        break
                    return None
        (DEBUG or not self.silent) and Log.note(self.name + " queue closed")
        return THREAD_STOP

    def pop_all(self):
        """
        NON-BLOCKING POP ALL IN QUEUE, IF ANY
        """
        with self.lock:
            output = list(self.queue)
            self.queue.clear()

        return output

    def pop_one(self):
        """
        NON-BLOCKING POP IN QUEUE, IF ANY
        """
        with self.lock:
            if self.closed:
                return THREAD_STOP
            elif not self.queue:
                return None
            else:
                v = self.queue.popleft()
                if v is THREAD_STOP:  # SENDING A STOP INTO THE QUEUE IS ALSO AN OPTION
                    self.closed.go()
                return v

    def close(self):
        self.closed.go()

    def commit(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
示例#2
0
class Queue(object):
    """
     SIMPLE MESSAGE QUEUE, multiprocessing.Queue REQUIRES SERIALIZATION, WHICH
     IS DIFFICULT TO USE JUST BETWEEN THREADS (SERIALIZATION REQUIRED)
    """
    def __init__(self,
                 name,
                 max=None,
                 silent=False,
                 unique=False,
                 allow_add_after_close=False):
        """
        max - LIMIT THE NUMBER IN THE QUEUE, IF TOO MANY add() AND extend() WILL BLOCK
        silent - COMPLAIN IF THE READERS ARE TOO SLOW
        unique - SET True IF YOU WANT ONLY ONE INSTANCE IN THE QUEUE AT A TIME
        """
        if not _Log:
            _late_import()

        self.name = name
        self.max = coalesce(max, 2**10)
        self.silent = silent
        self.allow_add_after_close = allow_add_after_close
        self.unique = unique
        self.please_stop = Signal("stop signal for " + name)
        self.lock = Lock("lock for queue " + name)
        self.queue = deque()
        self.next_warning = time()  # FOR DEBUGGING

    def __iter__(self):
        try:
            while True:
                value = self.pop(self.please_stop)
                if value is THREAD_STOP:
                    break
                if value is not None:
                    yield value
        except Exception as e:
            _Log.warning("Tell me about what happened here", e)

        if not self.silent:
            _Log.note("queue iterator is done")

    def add(self, value, timeout=None):
        with self.lock:
            if value is THREAD_STOP:
                # INSIDE THE lock SO THAT EXITING WILL RELEASE wait()
                self.queue.append(value)
                self.please_stop.go()
                return

            self._wait_for_queue_space(timeout=timeout)
            if self.please_stop and not self.allow_add_after_close:
                _Log.error("Do not add to closed queue")
            else:
                if self.unique:
                    if value not in self.queue:
                        self.queue.append(value)
                else:
                    self.queue.append(value)
        return self

    def push(self, value):
        """
        SNEAK value TO FRONT OF THE QUEUE
        """
        if self.please_stop and not self.allow_add_after_close:
            _Log.error("Do not push to closed queue")

        with self.lock:
            self._wait_for_queue_space()
            if not self.please_stop:
                self.queue.appendleft(value)
        return self

    def pop_message(self, till=None):
        """
        RETURN TUPLE (message, payload) CALLER IS RESPONSIBLE FOR CALLING message.delete() WHEN DONE
        DUMMY IMPLEMENTATION FOR DEBUGGING
        """

        if till is not None and not isinstance(till, Signal):
            _Log.error("Expecting a signal")
        return Null, self.pop(till=till)

    def extend(self, values):
        if self.please_stop and not self.allow_add_after_close:
            _Log.error("Do not push to closed queue")

        with self.lock:
            # ONCE THE queue IS BELOW LIMIT, ALLOW ADDING MORE
            self._wait_for_queue_space()
            if not self.please_stop:
                if self.unique:
                    for v in values:
                        if v is THREAD_STOP:
                            self.please_stop.go()
                            continue
                        if v not in self.queue:
                            self.queue.append(v)
                else:
                    for v in values:
                        if v is THREAD_STOP:
                            self.please_stop.go()
                            continue
                        self.queue.append(v)
        return self

    def _wait_for_queue_space(self, timeout=DEFAULT_WAIT_TIME):
        """
        EXPECT THE self.lock TO BE HAD, WAITS FOR self.queue TO HAVE A LITTLE SPACE
        """
        wait_time = 5

        now = time()
        if timeout != None:
            time_to_stop_waiting = now + timeout
        else:
            time_to_stop_waiting = Null

        if self.next_warning < now:
            self.next_warning = now + wait_time

        while not self.please_stop and len(self.queue) >= self.max:
            if now > time_to_stop_waiting:
                if not _Log:
                    _late_import()
                _Log.error(THREAD_TIMEOUT)

            if self.silent:
                self.lock.wait(Till(till=time_to_stop_waiting))
            else:
                self.lock.wait(Till(timeout=wait_time))
                if len(self.queue) > self.max:
                    now = time()
                    if self.next_warning < now:
                        self.next_warning = now + wait_time
                        _Log.alert(
                            "Queue by name of {{name|quote}} is full with ({{num}} items), thread(s) have been waiting {{wait_time}} sec",
                            name=self.name,
                            num=len(self.queue),
                            wait_time=wait_time)

    def __len__(self):
        with self.lock:
            return len(self.queue)

    def __nonzero__(self):
        with self.lock:
            return any(r != THREAD_STOP for r in self.queue)

    def pop(self, till=None):
        """
        WAIT FOR NEXT ITEM ON THE QUEUE
        RETURN THREAD_STOP IF QUEUE IS CLOSED
        RETURN None IF till IS REACHED AND QUEUE IS STILL EMPTY

        :param till:  A `Signal` to stop waiting and return None
        :return:  A value, or a THREAD_STOP or None
        """
        if till is not None and not isinstance(till, Signal):
            _Log.error("expecting a signal")

        with self.lock:
            while True:
                if self.queue:
                    value = self.queue.popleft()
                    return value
                if self.please_stop:
                    break
                if not self.lock.wait(till=till | self.please_stop):
                    if self.please_stop:
                        break
                    return None
        if DEBUG or not self.silent:
            _Log.note(self.name + " queue stopped")
        return THREAD_STOP

    def pop_all(self):
        """
        NON-BLOCKING POP ALL IN QUEUE, IF ANY
        """
        with self.lock:
            output = list(self.queue)
            self.queue.clear()

        return output

    def pop_one(self):
        """
        NON-BLOCKING POP IN QUEUE, IF ANY
        """
        with self.lock:
            if self.please_stop:
                return [THREAD_STOP]
            elif not self.queue:
                return None
            else:
                v = self.queue.pop()
                if v is THREAD_STOP:  # SENDING A STOP INTO THE QUEUE IS ALSO AN OPTION
                    self.please_stop.go()
                return v

    def close(self):
        with self.lock:
            self.please_stop.go()

    def commit(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()