|
@@ -1,1278 +0,0 @@
|
|
|
-# -*- coding: utf-8 -*-
|
|
|
-#
|
|
|
-# Module providing the `Pool` class for managing a process pool
|
|
|
-#
|
|
|
-# multiprocessing/pool.py
|
|
|
-#
|
|
|
-# Copyright (c) 2007-2008, R Oudkerk --- see COPYING.txt
|
|
|
-#
|
|
|
-from __future__ import absolute_import
|
|
|
-
|
|
|
-#
|
|
|
-# Imports
|
|
|
-#
|
|
|
-
|
|
|
-import collections
|
|
|
-import errno
|
|
|
-import itertools
|
|
|
-import logging
|
|
|
-import os
|
|
|
-import signal
|
|
|
-import sys
|
|
|
-import threading
|
|
|
-import time
|
|
|
-import Queue
|
|
|
-import warnings
|
|
|
-
|
|
|
-from multiprocessing import cpu_count, TimeoutError, Event
|
|
|
-from multiprocessing import util
|
|
|
-from multiprocessing.util import Finalize, debug
|
|
|
-
|
|
|
-from celery.datastructures import ExceptionInfo
|
|
|
-from celery.exceptions import SoftTimeLimitExceeded, TimeLimitExceeded
|
|
|
-from celery.exceptions import WorkerLostError
|
|
|
-
|
|
|
-from .process import Process
|
|
|
-
|
|
|
-_Semaphore = threading._Semaphore
|
|
|
-
|
|
|
-#
|
|
|
-# Constants representing the state of a pool
|
|
|
-#
|
|
|
-
|
|
|
-RUN = 0
|
|
|
-CLOSE = 1
|
|
|
-TERMINATE = 2
|
|
|
-
|
|
|
-#
|
|
|
-# Constants representing the state of a job
|
|
|
-#
|
|
|
-
|
|
|
-ACK = 0
|
|
|
-READY = 1
|
|
|
-
|
|
|
-# Signal used for soft time limits.
|
|
|
-SIG_SOFT_TIMEOUT = getattr(signal, "SIGUSR1", None)
|
|
|
-
|
|
|
-#
|
|
|
-# Miscellaneous
|
|
|
-#
|
|
|
-
|
|
|
-LOST_WORKER_TIMEOUT = 10.0
|
|
|
-
|
|
|
-job_counter = itertools.count()
|
|
|
-
|
|
|
-
|
|
|
-def mapstar(args):
|
|
|
- return map(*args)
|
|
|
-
|
|
|
-
|
|
|
-def error(msg, *args, **kwargs):
|
|
|
- if util._logger:
|
|
|
- util._logger.error(msg, *args, **kwargs)
|
|
|
-
|
|
|
-
|
|
|
-def safe_apply_callback(fun, *args):
|
|
|
- if fun:
|
|
|
- try:
|
|
|
- fun(*args)
|
|
|
- except BaseException, exc:
|
|
|
- error("Pool callback raised exception: %r", exc,
|
|
|
- exc_info=True)
|
|
|
-
|
|
|
-
|
|
|
-class LaxBoundedSemaphore(threading._Semaphore):
|
|
|
- """Semaphore that checks that # release is <= # acquires,
|
|
|
- but ignores if # releases >= value."""
|
|
|
-
|
|
|
- def __init__(self, value=1, verbose=None):
|
|
|
- _Semaphore.__init__(self, value, verbose)
|
|
|
- self._initial_value = value
|
|
|
-
|
|
|
- if sys.version_info >= (3, 0):
|
|
|
-
|
|
|
- def release(self):
|
|
|
- if self._value < self._initial_value:
|
|
|
- _Semaphore.release(self)
|
|
|
- if __debug__:
|
|
|
- self._note("%s.release: success, value=%s (unchanged)" % (
|
|
|
- self, self._value))
|
|
|
-
|
|
|
- def clear(self):
|
|
|
- while self._value < self._initial_value:
|
|
|
- _Semaphore.release(self)
|
|
|
- else:
|
|
|
-
|
|
|
- def release(self): # noqa
|
|
|
- if self._Semaphore__value < self._initial_value:
|
|
|
- _Semaphore.release(self)
|
|
|
- if __debug__:
|
|
|
- self._note("%s.release: success, value=%s (unchanged)" % (
|
|
|
- self, self._Semaphore__value))
|
|
|
-
|
|
|
- def clear(self): # noqa
|
|
|
- while self._Semaphore__value < self._initial_value:
|
|
|
- _Semaphore.release(self)
|
|
|
-
|
|
|
-#
|
|
|
-# Exceptions
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-class MaybeEncodingError(Exception):
|
|
|
- """Wraps unpickleable object."""
|
|
|
-
|
|
|
- def __init__(self, exc, value):
|
|
|
- self.exc = str(exc)
|
|
|
- self.value = repr(value)
|
|
|
- Exception.__init__(self, self.exc, self.value)
|
|
|
-
|
|
|
- def __repr__(self):
|
|
|
- return "<MaybeEncodingError: %s>" % str(self)
|
|
|
-
|
|
|
- def __str__(self):
|
|
|
- return "Error sending result: '%s'. Reason: '%s'." % (
|
|
|
- self.value, self.exc)
|
|
|
-
|
|
|
-
|
|
|
-class WorkersJoined(Exception):
|
|
|
- """All workers have terminated."""
|
|
|
-
|
|
|
-
|
|
|
-def soft_timeout_sighandler(signum, frame):
|
|
|
- raise SoftTimeLimitExceeded()
|
|
|
-
|
|
|
-#
|
|
|
-# Code run by worker processes
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-def worker(inqueue, outqueue, initializer=None, initargs=(),
|
|
|
- maxtasks=None, sentinel=None):
|
|
|
- # Re-init logging system.
|
|
|
- # Workaround for http://bugs.python.org/issue6721#msg140215
|
|
|
- # Python logging module uses RLock() objects which are broken after
|
|
|
- # fork. This can result in a deadlock (Issue #496).
|
|
|
- logger_names = logging.Logger.manager.loggerDict.keys()
|
|
|
- logger_names.append(None) # for root logger
|
|
|
- for name in logger_names:
|
|
|
- for handler in logging.getLogger(name).handlers:
|
|
|
- handler.createLock()
|
|
|
- logging._lock = threading.RLock()
|
|
|
-
|
|
|
- pid = os.getpid()
|
|
|
- assert maxtasks is None or (type(maxtasks) == int and maxtasks > 0)
|
|
|
- put = outqueue.put
|
|
|
- get = inqueue.get
|
|
|
-
|
|
|
- if hasattr(inqueue, '_reader'):
|
|
|
-
|
|
|
- def poll(timeout):
|
|
|
- if inqueue._reader.poll(timeout):
|
|
|
- return True, get()
|
|
|
- return False, None
|
|
|
- else:
|
|
|
-
|
|
|
- def poll(timeout): # noqa
|
|
|
- try:
|
|
|
- return True, get(timeout=timeout)
|
|
|
- except Queue.Empty:
|
|
|
- return False, None
|
|
|
-
|
|
|
- if hasattr(inqueue, '_writer'):
|
|
|
- inqueue._writer.close()
|
|
|
- outqueue._reader.close()
|
|
|
-
|
|
|
- if initializer is not None:
|
|
|
- initializer(*initargs)
|
|
|
-
|
|
|
- if SIG_SOFT_TIMEOUT is not None:
|
|
|
- signal.signal(SIG_SOFT_TIMEOUT, soft_timeout_sighandler)
|
|
|
-
|
|
|
- completed = 0
|
|
|
- while maxtasks is None or (maxtasks and completed < maxtasks):
|
|
|
- if sentinel is not None and sentinel.is_set():
|
|
|
- debug('worker got sentinel -- exiting')
|
|
|
- break
|
|
|
-
|
|
|
- try:
|
|
|
- ready, task = poll(1.0)
|
|
|
- if not ready:
|
|
|
- continue
|
|
|
- except (EOFError, IOError):
|
|
|
- debug('worker got EOFError or IOError -- exiting')
|
|
|
- break
|
|
|
-
|
|
|
- if task is None:
|
|
|
- debug('worker got sentinel -- exiting')
|
|
|
- break
|
|
|
-
|
|
|
- job, i, func, args, kwds = task
|
|
|
- put((ACK, (job, i, time.time(), pid)))
|
|
|
- try:
|
|
|
- result = (True, func(*args, **kwds))
|
|
|
- except Exception:
|
|
|
- result = (False, ExceptionInfo(sys.exc_info()))
|
|
|
- try:
|
|
|
- put((READY, (job, i, result)))
|
|
|
- except Exception, exc:
|
|
|
- _, _, tb = sys.exc_info()
|
|
|
- wrapped = MaybeEncodingError(exc, result[1])
|
|
|
- einfo = ExceptionInfo((MaybeEncodingError, wrapped, tb))
|
|
|
- put((READY, (job, i, (False, einfo))))
|
|
|
-
|
|
|
- completed += 1
|
|
|
- debug('worker exiting after %d tasks' % completed)
|
|
|
-
|
|
|
-#
|
|
|
-# Class representing a process pool
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-class PoolThread(threading.Thread):
|
|
|
-
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
- threading.Thread.__init__(self)
|
|
|
- self._state = RUN
|
|
|
- self.daemon = True
|
|
|
-
|
|
|
- def run(self):
|
|
|
- try:
|
|
|
- return self.body()
|
|
|
- except Exception, exc:
|
|
|
- error("Thread %r crashed: %r" % (self.__class__.__name__, exc, ),
|
|
|
- exc_info=True)
|
|
|
- os._exit(1)
|
|
|
-
|
|
|
- def terminate(self):
|
|
|
- self._state = TERMINATE
|
|
|
-
|
|
|
- def close(self):
|
|
|
- self._state = CLOSE
|
|
|
-
|
|
|
-
|
|
|
-class Supervisor(PoolThread):
|
|
|
-
|
|
|
- def __init__(self, pool):
|
|
|
- self.pool = pool
|
|
|
- super(Supervisor, self).__init__()
|
|
|
-
|
|
|
- def body(self):
|
|
|
- debug('worker handler starting')
|
|
|
- while self._state == RUN and self.pool._state == RUN:
|
|
|
- self.pool._maintain_pool()
|
|
|
- time.sleep(0.8)
|
|
|
- debug('worker handler exiting')
|
|
|
-
|
|
|
-
|
|
|
-class TaskHandler(PoolThread):
|
|
|
-
|
|
|
- def __init__(self, taskqueue, put, outqueue, pool):
|
|
|
- self.taskqueue = taskqueue
|
|
|
- self.put = put
|
|
|
- self.outqueue = outqueue
|
|
|
- self.pool = pool
|
|
|
- super(TaskHandler, self).__init__()
|
|
|
-
|
|
|
- def body(self):
|
|
|
- taskqueue = self.taskqueue
|
|
|
- outqueue = self.outqueue
|
|
|
- put = self.put
|
|
|
- pool = self.pool
|
|
|
-
|
|
|
- for taskseq, set_length in iter(taskqueue.get, None):
|
|
|
- i = -1
|
|
|
- for i, task in enumerate(taskseq):
|
|
|
- if self._state:
|
|
|
- debug('task handler found thread._state != RUN')
|
|
|
- break
|
|
|
- try:
|
|
|
- put(task)
|
|
|
- except IOError:
|
|
|
- debug('could not put task on queue')
|
|
|
- break
|
|
|
- else:
|
|
|
- if set_length:
|
|
|
- debug('doing set_length()')
|
|
|
- set_length(i + 1)
|
|
|
- continue
|
|
|
- break
|
|
|
- else:
|
|
|
- debug('task handler got sentinel')
|
|
|
-
|
|
|
- try:
|
|
|
- # tell result handler to finish when cache is empty
|
|
|
- debug('task handler sending sentinel to result handler')
|
|
|
- outqueue.put(None)
|
|
|
-
|
|
|
- # tell workers there is no more work
|
|
|
- debug('task handler sending sentinel to workers')
|
|
|
- for p in pool:
|
|
|
- put(None)
|
|
|
- except IOError:
|
|
|
- debug('task handler got IOError when sending sentinels')
|
|
|
-
|
|
|
- debug('task handler exiting')
|
|
|
-
|
|
|
-
|
|
|
-class TimeoutHandler(PoolThread):
|
|
|
-
|
|
|
- def __init__(self, processes, cache, t_soft, t_hard):
|
|
|
- self.processes = processes
|
|
|
- self.cache = cache
|
|
|
- self.t_soft = t_soft
|
|
|
- self.t_hard = t_hard
|
|
|
- super(TimeoutHandler, self).__init__()
|
|
|
-
|
|
|
- def body(self):
|
|
|
- processes = self.processes
|
|
|
- cache = self.cache
|
|
|
- t_hard, t_soft = self.t_hard, self.t_soft
|
|
|
- dirty = set()
|
|
|
-
|
|
|
- def _process_by_pid(pid):
|
|
|
- for index, process in enumerate(processes):
|
|
|
- if process.pid == pid:
|
|
|
- return process, index
|
|
|
- return None, None
|
|
|
-
|
|
|
- def _timed_out(start, timeout):
|
|
|
- if not start or not timeout:
|
|
|
- return False
|
|
|
- if time.time() >= start + timeout:
|
|
|
- return True
|
|
|
-
|
|
|
- def _on_soft_timeout(job, i, soft_timeout):
|
|
|
- debug('soft time limit exceeded for %i' % i)
|
|
|
- process, _index = _process_by_pid(job._worker_pid)
|
|
|
- if not process:
|
|
|
- return
|
|
|
-
|
|
|
- # Run timeout callback
|
|
|
- if job._timeout_callback is not None:
|
|
|
- job._timeout_callback(soft=True, timeout=soft_timeout)
|
|
|
-
|
|
|
- try:
|
|
|
- os.kill(job._worker_pid, SIG_SOFT_TIMEOUT)
|
|
|
- except OSError, exc:
|
|
|
- if exc.errno == errno.ESRCH:
|
|
|
- pass
|
|
|
- else:
|
|
|
- raise
|
|
|
-
|
|
|
- dirty.add(i)
|
|
|
-
|
|
|
- def _on_hard_timeout(job, i, hard_timeout):
|
|
|
- if job.ready():
|
|
|
- return
|
|
|
- debug('hard time limit exceeded for %i', i)
|
|
|
- # Remove from cache and set return value to an exception
|
|
|
- exc_info = None
|
|
|
- try:
|
|
|
- raise TimeLimitExceeded(hard_timeout)
|
|
|
- except TimeLimitExceeded:
|
|
|
- exc_info = sys.exc_info()
|
|
|
- job._set(i, (False, ExceptionInfo(exc_info)))
|
|
|
-
|
|
|
- # Remove from _pool
|
|
|
- process, _index = _process_by_pid(job._worker_pid)
|
|
|
-
|
|
|
- # Run timeout callback
|
|
|
- if job._timeout_callback is not None:
|
|
|
- job._timeout_callback(soft=False, timeout=hard_timeout)
|
|
|
- if process:
|
|
|
- process.terminate()
|
|
|
-
|
|
|
- # Inner-loop
|
|
|
- while self._state == RUN:
|
|
|
-
|
|
|
- # Remove dirty items not in cache anymore
|
|
|
- if dirty:
|
|
|
- dirty = set(k for k in dirty if k in cache)
|
|
|
-
|
|
|
- for i, job in cache.items():
|
|
|
- ack_time = job._time_accepted
|
|
|
- soft_timeout = job._soft_timeout
|
|
|
- if soft_timeout is None:
|
|
|
- soft_timeout = t_soft
|
|
|
- hard_timeout = job._timeout
|
|
|
- if hard_timeout is None:
|
|
|
- hard_timeout = t_hard
|
|
|
- if _timed_out(ack_time, hard_timeout):
|
|
|
- _on_hard_timeout(job, i, hard_timeout)
|
|
|
- elif i not in dirty and _timed_out(ack_time, soft_timeout):
|
|
|
- _on_soft_timeout(job, i, soft_timeout)
|
|
|
-
|
|
|
- time.sleep(1.0) # Don't waste CPU cycles.
|
|
|
-
|
|
|
- debug('timeout handler exiting')
|
|
|
-
|
|
|
-
|
|
|
-class ResultHandler(PoolThread):
|
|
|
-
|
|
|
- def __init__(self, outqueue, get, cache, poll,
|
|
|
- join_exited_workers, putlock):
|
|
|
- self.outqueue = outqueue
|
|
|
- self.get = get
|
|
|
- self.cache = cache
|
|
|
- self.poll = poll
|
|
|
- self.join_exited_workers = join_exited_workers
|
|
|
- self.putlock = putlock
|
|
|
- super(ResultHandler, self).__init__()
|
|
|
-
|
|
|
- def body(self):
|
|
|
- get = self.get
|
|
|
- outqueue = self.outqueue
|
|
|
- cache = self.cache
|
|
|
- poll = self.poll
|
|
|
- join_exited_workers = self.join_exited_workers
|
|
|
- putlock = self.putlock
|
|
|
-
|
|
|
- def on_ack(job, i, time_accepted, pid):
|
|
|
- try:
|
|
|
- cache[job]._ack(i, time_accepted, pid)
|
|
|
- except (KeyError, AttributeError):
|
|
|
- # Object gone or doesn't support _ack (e.g. IMAPIterator).
|
|
|
- pass
|
|
|
-
|
|
|
- def on_ready(job, i, obj):
|
|
|
- try:
|
|
|
- item = cache[job]
|
|
|
- except KeyError:
|
|
|
- return
|
|
|
- if not item.ready():
|
|
|
- if putlock is not None:
|
|
|
- putlock.release()
|
|
|
- try:
|
|
|
- item._set(i, obj)
|
|
|
- except KeyError:
|
|
|
- pass
|
|
|
-
|
|
|
- state_handlers = {ACK: on_ack, READY: on_ready}
|
|
|
-
|
|
|
- def on_state_change(task):
|
|
|
- state, args = task
|
|
|
- try:
|
|
|
- state_handlers[state](*args)
|
|
|
- except KeyError:
|
|
|
- debug("Unknown job state: %s (args=%s)" % (state, args))
|
|
|
-
|
|
|
- debug('result handler starting')
|
|
|
- while 1:
|
|
|
- try:
|
|
|
- ready, task = poll(1.0)
|
|
|
- except (IOError, EOFError), exc:
|
|
|
- debug('result handler got %r -- exiting' % (exc, ))
|
|
|
- return
|
|
|
-
|
|
|
- if self._state:
|
|
|
- assert self._state == TERMINATE
|
|
|
- debug('result handler found thread._state=TERMINATE')
|
|
|
- break
|
|
|
-
|
|
|
- if ready:
|
|
|
- if task is None:
|
|
|
- debug('result handler got sentinel')
|
|
|
- break
|
|
|
-
|
|
|
- on_state_change(task)
|
|
|
-
|
|
|
- time_terminate = None
|
|
|
- while cache and self._state != TERMINATE:
|
|
|
- try:
|
|
|
- ready, task = poll(1.0)
|
|
|
- except (IOError, EOFError), exc:
|
|
|
- debug('result handler got %r -- exiting' % (exc, ))
|
|
|
- return
|
|
|
-
|
|
|
- if ready:
|
|
|
- if task is None:
|
|
|
- debug('result handler ignoring extra sentinel')
|
|
|
- continue
|
|
|
-
|
|
|
- on_state_change(task)
|
|
|
- try:
|
|
|
- join_exited_workers(shutdown=True)
|
|
|
- except WorkersJoined:
|
|
|
- now = time.time()
|
|
|
- if not time_terminate:
|
|
|
- time_terminate = now
|
|
|
- else:
|
|
|
- if now - time_terminate > 5.0:
|
|
|
- debug('result handler exiting: timed out')
|
|
|
- break
|
|
|
- debug('result handler: all workers terminated, '
|
|
|
- 'timeout in %ss' % (
|
|
|
- abs(min(now - time_terminate - 5.0, 0))))
|
|
|
-
|
|
|
- if hasattr(outqueue, '_reader'):
|
|
|
- debug('ensuring that outqueue is not full')
|
|
|
- # If we don't make room available in outqueue then
|
|
|
- # attempts to add the sentinel (None) to outqueue may
|
|
|
- # block. There is guaranteed to be no more than 2 sentinels.
|
|
|
- try:
|
|
|
- for i in range(10):
|
|
|
- if not outqueue._reader.poll():
|
|
|
- break
|
|
|
- get()
|
|
|
- except (IOError, EOFError):
|
|
|
- pass
|
|
|
-
|
|
|
- debug('result handler exiting: len(cache)=%s, thread._state=%s',
|
|
|
- len(cache), self._state)
|
|
|
-
|
|
|
-
|
|
|
-class Pool(object):
|
|
|
- '''
|
|
|
- Class which supports an async version of the `apply()` builtin
|
|
|
- '''
|
|
|
- Process = Process
|
|
|
- Supervisor = Supervisor
|
|
|
- TaskHandler = TaskHandler
|
|
|
- TimeoutHandler = TimeoutHandler
|
|
|
- ResultHandler = ResultHandler
|
|
|
- SoftTimeLimitExceeded = SoftTimeLimitExceeded
|
|
|
-
|
|
|
- def __init__(self, processes=None, initializer=None, initargs=(),
|
|
|
- maxtasksperchild=None, timeout=None, soft_timeout=None,
|
|
|
- force_execv=False, lost_worker_timeout=LOST_WORKER_TIMEOUT):
|
|
|
- self._setup_queues()
|
|
|
- self._taskqueue = Queue.Queue()
|
|
|
- self._cache = {}
|
|
|
- self._state = RUN
|
|
|
- self.timeout = timeout
|
|
|
- self.soft_timeout = soft_timeout
|
|
|
- self._maxtasksperchild = maxtasksperchild
|
|
|
- self._initializer = initializer
|
|
|
- self._initargs = initargs
|
|
|
- self._force_execv = force_execv
|
|
|
- self.lost_worker_timeout = lost_worker_timeout or LOST_WORKER_TIMEOUT
|
|
|
-
|
|
|
- if soft_timeout and SIG_SOFT_TIMEOUT is None:
|
|
|
- warnings.warn(UserWarning("Soft timeouts are not supported: "
|
|
|
- "on this platform: It does not have the SIGUSR1 signal."))
|
|
|
- soft_timeout = None
|
|
|
-
|
|
|
- if processes is None:
|
|
|
- try:
|
|
|
- processes = cpu_count()
|
|
|
- except NotImplementedError:
|
|
|
- processes = 1
|
|
|
- self._processes = processes
|
|
|
-
|
|
|
- if initializer is not None and not hasattr(initializer, '__call__'):
|
|
|
- raise TypeError('initializer must be a callable')
|
|
|
-
|
|
|
- self._pool = []
|
|
|
- self._poolctrl = {}
|
|
|
- self._putlock = LaxBoundedSemaphore(self._processes)
|
|
|
- for i in range(processes):
|
|
|
- self._create_worker_process()
|
|
|
-
|
|
|
- self._worker_handler = self.Supervisor(self)
|
|
|
- self._worker_handler.start()
|
|
|
-
|
|
|
- self._task_handler = self.TaskHandler(self._taskqueue,
|
|
|
- self._quick_put,
|
|
|
- self._outqueue,
|
|
|
- self._pool)
|
|
|
- self._task_handler.start()
|
|
|
-
|
|
|
- # Thread killing timedout jobs.
|
|
|
- self._timeout_handler = None
|
|
|
- self._timeout_handler_mutex = threading.Lock()
|
|
|
- if self.timeout is not None or self.soft_timeout is not None:
|
|
|
- self._start_timeout_handler()
|
|
|
-
|
|
|
- # Thread processing results in the outqueue.
|
|
|
- self._result_handler = self.ResultHandler(self._outqueue,
|
|
|
- self._quick_get, self._cache,
|
|
|
- self._poll_result,
|
|
|
- self._join_exited_workers,
|
|
|
- self._putlock)
|
|
|
- self._result_handler.start()
|
|
|
-
|
|
|
- self._terminate = Finalize(
|
|
|
- self, self._terminate_pool,
|
|
|
- args=(self._taskqueue, self._inqueue, self._outqueue,
|
|
|
- self._pool, self._worker_handler, self._task_handler,
|
|
|
- self._result_handler, self._cache,
|
|
|
- self._timeout_handler),
|
|
|
- exitpriority=15,
|
|
|
- )
|
|
|
-
|
|
|
- def _create_worker_process(self):
|
|
|
- sentinel = Event()
|
|
|
- w = self.Process(
|
|
|
- force_execv=self._force_execv,
|
|
|
- target=worker,
|
|
|
- args=(self._inqueue, self._outqueue,
|
|
|
- self._initializer, self._initargs,
|
|
|
- self._maxtasksperchild,
|
|
|
- sentinel),
|
|
|
- )
|
|
|
- self._pool.append(w)
|
|
|
- w.name = w.name.replace('Process', 'PoolWorker')
|
|
|
- w.daemon = True
|
|
|
- w.start()
|
|
|
- self._poolctrl[w.pid] = sentinel
|
|
|
- return w
|
|
|
-
|
|
|
- def _join_exited_workers(self, shutdown=False):
|
|
|
- """Cleanup after any worker processes which have exited due to
|
|
|
- reaching their specified lifetime. Returns True if any workers were
|
|
|
- cleaned up.
|
|
|
- """
|
|
|
- now = None
|
|
|
- # The worker may have published a result before being terminated,
|
|
|
- # but we have no way to accurately tell if it did. So we wait for
|
|
|
- # _lost_worker_timeout seconds before we mark the job with
|
|
|
- # WorkerLostError.
|
|
|
- for job in [job for job in self._cache.values()
|
|
|
- if not job.ready() and job._worker_lost]:
|
|
|
- now = now or time.time()
|
|
|
- if now - job._worker_lost > job._lost_worker_timeout:
|
|
|
- exc_info = None
|
|
|
- try:
|
|
|
- raise WorkerLostError("Worker exited prematurely.")
|
|
|
- except WorkerLostError:
|
|
|
- exc_info = ExceptionInfo(sys.exc_info())
|
|
|
- job._set(None, (False, exc_info))
|
|
|
-
|
|
|
- if shutdown and not len(self._pool):
|
|
|
- raise WorkersJoined()
|
|
|
-
|
|
|
- cleaned = []
|
|
|
- for i in reversed(range(len(self._pool))):
|
|
|
- worker = self._pool[i]
|
|
|
- if worker.exitcode is not None:
|
|
|
- # worker exited
|
|
|
- debug('Supervisor: cleaning up worker %d' % i)
|
|
|
- worker.join()
|
|
|
- debug('Supervisor: worked %d joined' % i)
|
|
|
- cleaned.append(worker.pid)
|
|
|
- del self._pool[i]
|
|
|
- del self._poolctrl[worker.pid]
|
|
|
- if cleaned:
|
|
|
- for job in self._cache.values():
|
|
|
- for worker_pid in job.worker_pids():
|
|
|
- if worker_pid in cleaned and not job.ready():
|
|
|
- job._worker_lost = time.time()
|
|
|
- continue
|
|
|
- if self._putlock is not None:
|
|
|
- for worker in cleaned:
|
|
|
- self._putlock.release()
|
|
|
- return True
|
|
|
- return False
|
|
|
-
|
|
|
- def shrink(self, n=1):
|
|
|
- for i, worker in enumerate(self._iterinactive()):
|
|
|
- self._processes -= 1
|
|
|
- if self._putlock:
|
|
|
- self._putlock._initial_value -= 1
|
|
|
- self._putlock.acquire()
|
|
|
- worker.terminate()
|
|
|
- if i == n - 1:
|
|
|
- return
|
|
|
- raise ValueError("Can't shrink pool. All processes busy!")
|
|
|
-
|
|
|
- def grow(self, n=1):
|
|
|
- for i in xrange(n):
|
|
|
- #assert len(self._pool) == self._processes
|
|
|
- self._processes += 1
|
|
|
- if self._putlock:
|
|
|
- cond = self._putlock._Semaphore__cond
|
|
|
- cond.acquire()
|
|
|
- try:
|
|
|
- self._putlock._initial_value += 1
|
|
|
- self._putlock._Semaphore__value += 1
|
|
|
- cond.notify()
|
|
|
- finally:
|
|
|
- cond.release()
|
|
|
-
|
|
|
- def _iterinactive(self):
|
|
|
- for worker in self._pool:
|
|
|
- if not self._worker_active(worker):
|
|
|
- yield worker
|
|
|
- raise StopIteration()
|
|
|
-
|
|
|
- def _worker_active(self, worker):
|
|
|
- for job in self._cache.values():
|
|
|
- if worker.pid in job.worker_pids():
|
|
|
- return True
|
|
|
- return False
|
|
|
-
|
|
|
- def _repopulate_pool(self):
|
|
|
- """Bring the number of pool processes up to the specified number,
|
|
|
- for use after reaping workers which have exited.
|
|
|
- """
|
|
|
- for i in range(self._processes - len(self._pool)):
|
|
|
- if self._state != RUN:
|
|
|
- return
|
|
|
- self._create_worker_process()
|
|
|
- debug('added worker')
|
|
|
-
|
|
|
- def _maintain_pool(self):
|
|
|
- """"Clean up any exited workers and start replacements for them.
|
|
|
- """
|
|
|
- self._join_exited_workers()
|
|
|
- self._repopulate_pool()
|
|
|
-
|
|
|
- def _setup_queues(self):
|
|
|
- from multiprocessing.queues import SimpleQueue
|
|
|
- self._inqueue = SimpleQueue()
|
|
|
- self._outqueue = SimpleQueue()
|
|
|
- self._quick_put = self._inqueue._writer.send
|
|
|
- self._quick_get = self._outqueue._reader.recv
|
|
|
-
|
|
|
- def _poll_result(timeout):
|
|
|
- if self._outqueue._reader.poll(timeout):
|
|
|
- return True, self._quick_get()
|
|
|
- return False, None
|
|
|
- self._poll_result = _poll_result
|
|
|
-
|
|
|
- def _start_timeout_handler(self):
|
|
|
- # ensure more than one thread does not start the timeout handler
|
|
|
- # thread at once.
|
|
|
- self._timeout_handler_mutex.acquire()
|
|
|
- try:
|
|
|
- if self._timeout_handler is None:
|
|
|
- self._timeout_handler = self.TimeoutHandler(
|
|
|
- self._pool, self._cache,
|
|
|
- self.soft_timeout, self.timeout)
|
|
|
- self._timeout_handler.start()
|
|
|
- finally:
|
|
|
- self._timeout_handler_mutex.release()
|
|
|
-
|
|
|
- def apply(self, func, args=(), kwds={}):
|
|
|
- '''
|
|
|
- Equivalent of `apply()` builtin
|
|
|
- '''
|
|
|
- assert self._state == RUN
|
|
|
- return self.apply_async(func, args, kwds).get()
|
|
|
-
|
|
|
- def map(self, func, iterable, chunksize=None):
|
|
|
- '''
|
|
|
- Equivalent of `map()` builtin
|
|
|
- '''
|
|
|
- assert self._state == RUN
|
|
|
- return self.map_async(func, iterable, chunksize).get()
|
|
|
-
|
|
|
- def imap(self, func, iterable, chunksize=1, lost_worker_timeout=None):
|
|
|
- '''
|
|
|
- Equivalent of `itertools.imap()` -- can be MUCH slower
|
|
|
- than `Pool.map()`
|
|
|
- '''
|
|
|
- assert self._state == RUN
|
|
|
- lost_worker_timeout = lost_worker_timeout or self.lost_worker_timeout
|
|
|
- if chunksize == 1:
|
|
|
- result = IMapIterator(self._cache,
|
|
|
- lost_worker_timeout=lost_worker_timeout)
|
|
|
- self._taskqueue.put((((result._job, i, func, (x,), {})
|
|
|
- for i, x in enumerate(iterable)), result._set_length))
|
|
|
- return result
|
|
|
- else:
|
|
|
- assert chunksize > 1
|
|
|
- task_batches = Pool._get_tasks(func, iterable, chunksize)
|
|
|
- result = IMapIterator(self._cache,
|
|
|
- lost_worker_timeout=lost_worker_timeout)
|
|
|
- self._taskqueue.put((((result._job, i, mapstar, (x,), {})
|
|
|
- for i, x in enumerate(task_batches)), result._set_length))
|
|
|
- return (item for chunk in result for item in chunk)
|
|
|
-
|
|
|
- def imap_unordered(self, func, iterable, chunksize=1,
|
|
|
- lost_worker_timeout=None):
|
|
|
- '''
|
|
|
- Like `imap()` method but ordering of results is arbitrary
|
|
|
- '''
|
|
|
- assert self._state == RUN
|
|
|
- lost_worker_timeout = lost_worker_timeout or self.lost_worker_timeout
|
|
|
- if chunksize == 1:
|
|
|
- result = IMapUnorderedIterator(self._cache,
|
|
|
- lost_worker_timeout=lost_worker_timeout)
|
|
|
- self._taskqueue.put((((result._job, i, func, (x,), {})
|
|
|
- for i, x in enumerate(iterable)), result._set_length))
|
|
|
- return result
|
|
|
- else:
|
|
|
- assert chunksize > 1
|
|
|
- task_batches = Pool._get_tasks(func, iterable, chunksize)
|
|
|
- result = IMapUnorderedIterator(self._cache,
|
|
|
- lost_worker_timeout=lost_worker_timeout)
|
|
|
- self._taskqueue.put((((result._job, i, mapstar, (x,), {})
|
|
|
- for i, x in enumerate(task_batches)), result._set_length))
|
|
|
- return (item for chunk in result for item in chunk)
|
|
|
-
|
|
|
- def apply_async(self, func, args=(), kwds={},
|
|
|
- callback=None, accept_callback=None, timeout_callback=None,
|
|
|
- waitforslot=False, error_callback=None,
|
|
|
- soft_timeout=None, timeout=None, lost_worker_timeout=None):
|
|
|
- '''
|
|
|
- Asynchronous equivalent of `apply()` builtin.
|
|
|
-
|
|
|
- Callback is called when the functions return value is ready.
|
|
|
- The accept callback is called when the job is accepted to be executed.
|
|
|
-
|
|
|
- Simplified the flow is like this:
|
|
|
-
|
|
|
- >>> if accept_callback:
|
|
|
- ... accept_callback()
|
|
|
- >>> retval = func(*args, **kwds)
|
|
|
- >>> if callback:
|
|
|
- ... callback(retval)
|
|
|
-
|
|
|
- '''
|
|
|
- assert self._state == RUN
|
|
|
- lost_worker_timeout = lost_worker_timeout or self.lost_worker_timeout
|
|
|
- if soft_timeout and SIG_SOFT_TIMEOUT is None:
|
|
|
- warnings.warn(UserWarning("Soft timeouts are not supported: "
|
|
|
- "on this platform: It does not have the SIGUSR1 signal."))
|
|
|
- soft_timeout = None
|
|
|
- if waitforslot and self._putlock is not None and self._state == RUN:
|
|
|
- self._putlock.acquire()
|
|
|
- if self._state == RUN:
|
|
|
- result = ApplyResult(self._cache, callback,
|
|
|
- accept_callback, timeout_callback,
|
|
|
- error_callback, soft_timeout, timeout,
|
|
|
- lost_worker_timeout)
|
|
|
- if timeout or soft_timeout:
|
|
|
- # start the timeout handler thread when required.
|
|
|
- self._start_timeout_handler()
|
|
|
- self._taskqueue.put(([(result._job, None,
|
|
|
- func, args, kwds)], None))
|
|
|
- return result
|
|
|
-
|
|
|
- def map_async(self, func, iterable, chunksize=None, callback=None):
|
|
|
- '''
|
|
|
- Asynchronous equivalent of `map()` builtin
|
|
|
- '''
|
|
|
- assert self._state == RUN
|
|
|
- if not hasattr(iterable, '__len__'):
|
|
|
- iterable = list(iterable)
|
|
|
-
|
|
|
- if chunksize is None:
|
|
|
- chunksize, extra = divmod(len(iterable), len(self._pool) * 4)
|
|
|
- if extra:
|
|
|
- chunksize += 1
|
|
|
- if len(iterable) == 0:
|
|
|
- chunksize = 0
|
|
|
-
|
|
|
- task_batches = Pool._get_tasks(func, iterable, chunksize)
|
|
|
- result = MapResult(self._cache, chunksize, len(iterable), callback)
|
|
|
- self._taskqueue.put((((result._job, i, mapstar, (x,), {})
|
|
|
- for i, x in enumerate(task_batches)), None))
|
|
|
- return result
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def _get_tasks(func, it, size):
|
|
|
- it = iter(it)
|
|
|
- while 1:
|
|
|
- x = tuple(itertools.islice(it, size))
|
|
|
- if not x:
|
|
|
- return
|
|
|
- yield (func, x)
|
|
|
-
|
|
|
- def __reduce__(self):
|
|
|
- raise NotImplementedError(
|
|
|
- 'pool objects cannot be passed between '
|
|
|
- 'processes or pickled')
|
|
|
-
|
|
|
- def close(self):
|
|
|
- debug('closing pool')
|
|
|
- if self._state == RUN:
|
|
|
- self._state = CLOSE
|
|
|
- self._worker_handler.close()
|
|
|
- self._worker_handler.join()
|
|
|
- self._taskqueue.put(None)
|
|
|
- if self._putlock:
|
|
|
- self._putlock.clear()
|
|
|
-
|
|
|
- def terminate(self):
|
|
|
- debug('terminating pool')
|
|
|
- self._state = TERMINATE
|
|
|
- self._worker_handler.terminate()
|
|
|
- self._terminate()
|
|
|
-
|
|
|
- def join(self):
|
|
|
- assert self._state in (CLOSE, TERMINATE)
|
|
|
- debug('joining worker handler')
|
|
|
- self._worker_handler.join()
|
|
|
- debug('joining task handler')
|
|
|
- self._task_handler.join()
|
|
|
- debug('joining result handler')
|
|
|
- self._result_handler.join()
|
|
|
- debug('result handler joined')
|
|
|
- for i, p in enumerate(self._pool):
|
|
|
- debug('joining worker %s/%s (%r)' % (i, len(self._pool), p, ))
|
|
|
- p.join()
|
|
|
-
|
|
|
- def restart(self):
|
|
|
- for e in self._poolctrl.itervalues():
|
|
|
- e.set()
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def _help_stuff_finish(inqueue, task_handler, size):
|
|
|
- # task_handler may be blocked trying to put items on inqueue
|
|
|
- debug('removing tasks from inqueue until task handler finished')
|
|
|
- inqueue._rlock.acquire()
|
|
|
- while task_handler.is_alive() and inqueue._reader.poll():
|
|
|
- inqueue._reader.recv()
|
|
|
- time.sleep(0)
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def _terminate_pool(cls, taskqueue, inqueue, outqueue, pool,
|
|
|
- worker_handler, task_handler,
|
|
|
- result_handler, cache, timeout_handler):
|
|
|
-
|
|
|
- # this is guaranteed to only be called once
|
|
|
- debug('finalizing pool')
|
|
|
-
|
|
|
- worker_handler.terminate()
|
|
|
-
|
|
|
- task_handler.terminate()
|
|
|
- taskqueue.put(None) # sentinel
|
|
|
-
|
|
|
- debug('helping task handler/workers to finish')
|
|
|
- cls._help_stuff_finish(inqueue, task_handler, len(pool))
|
|
|
-
|
|
|
- result_handler.terminate()
|
|
|
- outqueue.put(None) # sentinel
|
|
|
-
|
|
|
- if timeout_handler is not None:
|
|
|
- timeout_handler.terminate()
|
|
|
-
|
|
|
- # Terminate workers which haven't already finished
|
|
|
- if pool and hasattr(pool[0], 'terminate'):
|
|
|
- debug('terminating workers')
|
|
|
- for p in pool:
|
|
|
- if p.exitcode is None:
|
|
|
- p.terminate()
|
|
|
-
|
|
|
- debug('joining task handler')
|
|
|
- task_handler.join(1e100)
|
|
|
-
|
|
|
- debug('joining result handler')
|
|
|
- result_handler.join(1e100)
|
|
|
-
|
|
|
- if timeout_handler is not None:
|
|
|
- debug('joining timeout handler')
|
|
|
- timeout_handler.join(1e100)
|
|
|
-
|
|
|
- if pool and hasattr(pool[0], 'terminate'):
|
|
|
- debug('joining pool workers')
|
|
|
- for p in pool:
|
|
|
- if p.is_alive():
|
|
|
- # worker has not yet exited
|
|
|
- debug('cleaning up worker %d' % p.pid)
|
|
|
- p.join()
|
|
|
- debug('pool workers joined')
|
|
|
-DynamicPool = Pool
|
|
|
-
|
|
|
-#
|
|
|
-# Class whose instances are returned by `Pool.apply_async()`
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-class ApplyResult(object):
|
|
|
- _worker_lost = None
|
|
|
-
|
|
|
- def __init__(self, cache, callback, accept_callback=None,
|
|
|
- timeout_callback=None, error_callback=None, soft_timeout=None,
|
|
|
- timeout=None, lost_worker_timeout=LOST_WORKER_TIMEOUT):
|
|
|
- self._mutex = threading.Lock()
|
|
|
- self._cond = threading.Condition(threading.Lock())
|
|
|
- self._job = job_counter.next()
|
|
|
- self._cache = cache
|
|
|
- self._ready = False
|
|
|
- self._callback = callback
|
|
|
- self._accept_callback = accept_callback
|
|
|
- self._errback = error_callback
|
|
|
- self._timeout_callback = timeout_callback
|
|
|
- self._timeout = timeout
|
|
|
- self._soft_timeout = soft_timeout
|
|
|
- self._lost_worker_timeout = lost_worker_timeout
|
|
|
-
|
|
|
- self._accepted = False
|
|
|
- self._worker_pid = None
|
|
|
- self._time_accepted = None
|
|
|
- cache[self._job] = self
|
|
|
-
|
|
|
- def ready(self):
|
|
|
- return self._ready
|
|
|
-
|
|
|
- def accepted(self):
|
|
|
- return self._accepted
|
|
|
-
|
|
|
- def successful(self):
|
|
|
- assert self._ready
|
|
|
- return self._success
|
|
|
-
|
|
|
- def worker_pids(self):
|
|
|
- return filter(None, [self._worker_pid])
|
|
|
-
|
|
|
- def wait(self, timeout=None):
|
|
|
- self._cond.acquire()
|
|
|
- try:
|
|
|
- if not self._ready:
|
|
|
- self._cond.wait(timeout)
|
|
|
- finally:
|
|
|
- self._cond.release()
|
|
|
-
|
|
|
- def get(self, timeout=None):
|
|
|
- self.wait(timeout)
|
|
|
- if not self._ready:
|
|
|
- raise TimeoutError
|
|
|
- if self._success:
|
|
|
- return self._value
|
|
|
- else:
|
|
|
- raise self._value
|
|
|
-
|
|
|
- def _set(self, i, obj):
|
|
|
- self._mutex.acquire()
|
|
|
- try:
|
|
|
- self._success, self._value = obj
|
|
|
- self._cond.acquire()
|
|
|
- try:
|
|
|
- self._ready = True
|
|
|
- self._cond.notify()
|
|
|
- finally:
|
|
|
- self._cond.release()
|
|
|
- if self._accepted:
|
|
|
- self._cache.pop(self._job, None)
|
|
|
-
|
|
|
- # apply callbacks last
|
|
|
- if self._callback and self._success:
|
|
|
- safe_apply_callback(
|
|
|
- self._callback, self._value)
|
|
|
- if self._errback and not self._success:
|
|
|
- safe_apply_callback(
|
|
|
- self._errback, self._value)
|
|
|
- finally:
|
|
|
- self._mutex.release()
|
|
|
-
|
|
|
- def _ack(self, i, time_accepted, pid):
|
|
|
- self._mutex.acquire()
|
|
|
- try:
|
|
|
- self._accepted = True
|
|
|
- self._time_accepted = time_accepted
|
|
|
- self._worker_pid = pid
|
|
|
- if self._ready:
|
|
|
- self._cache.pop(self._job, None)
|
|
|
- if self._accept_callback:
|
|
|
- safe_apply_callback(
|
|
|
- self._accept_callback, pid, time_accepted)
|
|
|
- finally:
|
|
|
- self._mutex.release()
|
|
|
-
|
|
|
-#
|
|
|
-# Class whose instances are returned by `Pool.map_async()`
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-class MapResult(ApplyResult):
|
|
|
-
|
|
|
- def __init__(self, cache, chunksize, length, callback):
|
|
|
- ApplyResult.__init__(self, cache, callback)
|
|
|
- self._success = True
|
|
|
- self._length = length
|
|
|
- self._value = [None] * length
|
|
|
- self._accepted = [False] * length
|
|
|
- self._worker_pid = [None] * length
|
|
|
- self._time_accepted = [None] * length
|
|
|
- self._chunksize = chunksize
|
|
|
- if chunksize <= 0:
|
|
|
- self._number_left = 0
|
|
|
- self._ready = True
|
|
|
- else:
|
|
|
- self._number_left = length // chunksize + bool(length % chunksize)
|
|
|
-
|
|
|
- def _set(self, i, success_result):
|
|
|
- success, result = success_result
|
|
|
- if success:
|
|
|
- self._value[i * self._chunksize:(i + 1) * self._chunksize] = result
|
|
|
- self._number_left -= 1
|
|
|
- if self._number_left == 0:
|
|
|
- if self._callback:
|
|
|
- self._callback(self._value)
|
|
|
- if self._accepted:
|
|
|
- self._cache.pop(self._job, None)
|
|
|
- self._cond.acquire()
|
|
|
- try:
|
|
|
- self._ready = True
|
|
|
- self._cond.notify()
|
|
|
- finally:
|
|
|
- self._cond.release()
|
|
|
-
|
|
|
- else:
|
|
|
- self._success = False
|
|
|
- self._value = result
|
|
|
- if self._accepted:
|
|
|
- self._cache.pop(self._job, None)
|
|
|
- self._cond.acquire()
|
|
|
- try:
|
|
|
- self._ready = True
|
|
|
- self._cond.notify()
|
|
|
- finally:
|
|
|
- self._cond.release()
|
|
|
-
|
|
|
- def _ack(self, i, time_accepted, pid):
|
|
|
- start = i * self._chunksize
|
|
|
- stop = (i + 1) * self._chunksize
|
|
|
- for j in range(start, stop):
|
|
|
- self._accepted[j] = True
|
|
|
- self._worker_pid[j] = pid
|
|
|
- self._time_accepted[j] = time_accepted
|
|
|
- if self._ready:
|
|
|
- self._cache.pop(self._job, None)
|
|
|
-
|
|
|
- def accepted(self):
|
|
|
- return all(self._accepted)
|
|
|
-
|
|
|
- def worker_pids(self):
|
|
|
- return filter(None, self._worker_pid)
|
|
|
-
|
|
|
-#
|
|
|
-# Class whose instances are returned by `Pool.imap()`
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-class IMapIterator(object):
|
|
|
- _worker_lost = None
|
|
|
-
|
|
|
- def __init__(self, cache, lost_worker_timeout=LOST_WORKER_TIMEOUT):
|
|
|
- self._cond = threading.Condition(threading.Lock())
|
|
|
- self._job = job_counter.next()
|
|
|
- self._cache = cache
|
|
|
- self._items = collections.deque()
|
|
|
- self._index = 0
|
|
|
- self._length = None
|
|
|
- self._ready = False
|
|
|
- self._unsorted = {}
|
|
|
- self._worker_pids = []
|
|
|
- self._lost_worker_timeout = lost_worker_timeout
|
|
|
- cache[self._job] = self
|
|
|
-
|
|
|
- def __iter__(self):
|
|
|
- return self
|
|
|
-
|
|
|
- def next(self, timeout=None):
|
|
|
- self._cond.acquire()
|
|
|
- try:
|
|
|
- try:
|
|
|
- item = self._items.popleft()
|
|
|
- except IndexError:
|
|
|
- if self._index == self._length:
|
|
|
- self._ready = True
|
|
|
- raise StopIteration
|
|
|
- self._cond.wait(timeout)
|
|
|
- try:
|
|
|
- item = self._items.popleft()
|
|
|
- except IndexError:
|
|
|
- if self._index == self._length:
|
|
|
- self._ready = True
|
|
|
- raise StopIteration
|
|
|
- raise TimeoutError
|
|
|
- finally:
|
|
|
- self._cond.release()
|
|
|
-
|
|
|
- success, value = item
|
|
|
- if success:
|
|
|
- return value
|
|
|
- raise Exception(value)
|
|
|
-
|
|
|
- __next__ = next # XXX
|
|
|
-
|
|
|
- def _set(self, i, obj):
|
|
|
- self._cond.acquire()
|
|
|
- try:
|
|
|
- if self._index == i:
|
|
|
- self._items.append(obj)
|
|
|
- self._index += 1
|
|
|
- while self._index in self._unsorted:
|
|
|
- obj = self._unsorted.pop(self._index)
|
|
|
- self._items.append(obj)
|
|
|
- self._index += 1
|
|
|
- self._cond.notify()
|
|
|
- else:
|
|
|
- self._unsorted[i] = obj
|
|
|
-
|
|
|
- if self._index == self._length:
|
|
|
- self._ready = True
|
|
|
- del self._cache[self._job]
|
|
|
- finally:
|
|
|
- self._cond.release()
|
|
|
-
|
|
|
- def _set_length(self, length):
|
|
|
- self._cond.acquire()
|
|
|
- try:
|
|
|
- self._length = length
|
|
|
- if self._index == self._length:
|
|
|
- self._ready = True
|
|
|
- self._cond.notify()
|
|
|
- del self._cache[self._job]
|
|
|
- finally:
|
|
|
- self._cond.release()
|
|
|
-
|
|
|
- def _ack(self, i, time_accepted, pid):
|
|
|
- self._worker_pids.append(pid)
|
|
|
-
|
|
|
- def ready(self):
|
|
|
- return self._ready
|
|
|
-
|
|
|
- def worker_pids(self):
|
|
|
- return self._worker_pids
|
|
|
-
|
|
|
-#
|
|
|
-# Class whose instances are returned by `Pool.imap_unordered()`
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-class IMapUnorderedIterator(IMapIterator):
|
|
|
-
|
|
|
- def _set(self, i, obj):
|
|
|
- self._cond.acquire()
|
|
|
- try:
|
|
|
- self._items.append(obj)
|
|
|
- self._index += 1
|
|
|
- self._cond.notify()
|
|
|
- if self._index == self._length:
|
|
|
- self._ready = True
|
|
|
- del self._cache[self._job]
|
|
|
- finally:
|
|
|
- self._cond.release()
|
|
|
-
|
|
|
-#
|
|
|
-#
|
|
|
-#
|
|
|
-
|
|
|
-
|
|
|
-class ThreadPool(Pool):
|
|
|
-
|
|
|
- from multiprocessing.dummy import Process as DummyProcess
|
|
|
- Process = DummyProcess
|
|
|
-
|
|
|
- def __init__(self, processes=None, initializer=None, initargs=()):
|
|
|
- Pool.__init__(self, processes, initializer, initargs)
|
|
|
-
|
|
|
- def _setup_queues(self):
|
|
|
- self._inqueue = Queue.Queue()
|
|
|
- self._outqueue = Queue.Queue()
|
|
|
- self._quick_put = self._inqueue.put
|
|
|
- self._quick_get = self._outqueue.get
|
|
|
-
|
|
|
- def _poll_result(timeout):
|
|
|
- try:
|
|
|
- return True, self._quick_get(timeout=timeout)
|
|
|
- except Queue.Empty:
|
|
|
- return False, None
|
|
|
- self._poll_result = _poll_result
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def _help_stuff_finish(inqueue, task_handler, size):
|
|
|
- # put sentinels at head of inqueue to make workers finish
|
|
|
- inqueue.not_empty.acquire()
|
|
|
- try:
|
|
|
- inqueue.queue.clear()
|
|
|
- inqueue.queue.extend([None] * size)
|
|
|
- inqueue.not_empty.notify_all()
|
|
|
- finally:
|
|
|
- inqueue.not_empty.release()
|