|
@@ -10,6 +10,7 @@ import uuid
|
|
|
|
|
|
from multiprocessing.pool import RUN as POOL_STATE_RUN
|
|
|
from celery.datastructures import ExceptionInfo
|
|
|
+from functools import partial as curry
|
|
|
|
|
|
|
|
|
class TaskPool(object):
|
|
@@ -39,49 +40,19 @@ class TaskPool(object):
|
|
|
self._pool = None
|
|
|
self._processes = None
|
|
|
|
|
|
- def run(self):
|
|
|
+ def start(self):
|
|
|
"""Run the task pool.
|
|
|
|
|
|
Will pre-fork all workers so they're ready to accept tasks.
|
|
|
|
|
|
"""
|
|
|
- self._start()
|
|
|
-
|
|
|
- def _start(self):
|
|
|
- """INTERNAL: Starts the pool. Used by :meth:`run`."""
|
|
|
self._processes = {}
|
|
|
self._pool = multiprocessing.Pool(processes=self.limit)
|
|
|
|
|
|
- def terminate(self):
|
|
|
+ def stop(self):
|
|
|
"""Terminate the pool."""
|
|
|
self._pool.terminate()
|
|
|
|
|
|
- def _terminate_and_restart(self):
|
|
|
- """INTERNAL: Terminate and restart the pool."""
|
|
|
- try:
|
|
|
- self.terminate()
|
|
|
- except OSError:
|
|
|
- pass
|
|
|
- self._start()
|
|
|
-
|
|
|
- def _restart(self):
|
|
|
- """INTERNAL: Close and restart the pool."""
|
|
|
- self.logger.info("Closing and restarting the pool...")
|
|
|
- self._pool.close()
|
|
|
- timeout_thread = threading.Timer(30.0, self._terminate_and_restart)
|
|
|
- timeout_thread.start()
|
|
|
- self._pool.join()
|
|
|
- timeout_thread.cancel()
|
|
|
- self._start()
|
|
|
-
|
|
|
- def _pool_is_running(self):
|
|
|
- """Check if the pool is in the run state.
|
|
|
-
|
|
|
- :returns: ``True`` if the pool is running.
|
|
|
-
|
|
|
- """
|
|
|
- return self._pool._state == POOL_STATE_RUN
|
|
|
-
|
|
|
def apply_async(self, target, args=None, kwargs=None, callbacks=None,
|
|
|
errbacks=None, on_acknowledge=None, meta=None):
|
|
|
"""Equivalent of the :func:``apply`` built-in function.
|
|
@@ -97,56 +68,30 @@ class TaskPool(object):
|
|
|
meta = meta or {}
|
|
|
tid = str(uuid.uuid4())
|
|
|
|
|
|
- if not self._pool_is_running():
|
|
|
- self._start()
|
|
|
-
|
|
|
self._processed_total = self._process_counter.next()
|
|
|
|
|
|
- on_return = lambda r: self.on_return(r, tid, callbacks, errbacks, meta)
|
|
|
-
|
|
|
+ on_return = curry(self.on_return, tid, callbacks, errbacks, meta)
|
|
|
|
|
|
if self.full():
|
|
|
self.wait_for_result()
|
|
|
+
|
|
|
result = self._pool.apply_async(target, args, kwargs,
|
|
|
- callback=on_return)
|
|
|
+ callback=on_return)
|
|
|
if on_acknowledge:
|
|
|
on_acknowledge()
|
|
|
- self.add(result, callbacks, errbacks, tid, meta)
|
|
|
+
|
|
|
+ self._processes[tid] = [result, callbacks, errbacks, meta]
|
|
|
|
|
|
return result
|
|
|
|
|
|
- def on_return(self, ret_val, tid, callbacks, errbacks, meta):
|
|
|
+ def on_return(self, tid, callbacks, errbacks, meta, ret_value):
|
|
|
"""What to do when the process returns."""
|
|
|
try:
|
|
|
del(self._processes[tid])
|
|
|
except KeyError:
|
|
|
pass
|
|
|
else:
|
|
|
- self.on_ready(ret_val, callbacks, errbacks, meta)
|
|
|
-
|
|
|
- def add(self, result, callbacks, errbacks, tid, meta):
|
|
|
- """Add a process to the queue.
|
|
|
-
|
|
|
- If the queue is full, it will wait for the first task to finish,
|
|
|
- collects its result and remove it from the queue, so it's ready
|
|
|
- to accept new processes.
|
|
|
-
|
|
|
- :param result: A :class:`multiprocessing.AsyncResult` instance, as
|
|
|
- returned by :meth:`multiprocessing.Pool.apply_async`.
|
|
|
-
|
|
|
- :option callbacks: List of callbacks to execute if the task was
|
|
|
- successful. Must have the function signature:
|
|
|
- ``mycallback(result, meta)``
|
|
|
-
|
|
|
- :option errbacks: List of errbacks to execute if the task raised
|
|
|
- and exception. Must have the function signature:
|
|
|
- ``myerrback(exc, meta)``.
|
|
|
-
|
|
|
- :option tid: The tid for this task (unqiue pool id).
|
|
|
-
|
|
|
- """
|
|
|
-
|
|
|
- self._processes[tid] = [result, callbacks, errbacks, meta]
|
|
|
+ self.on_ready(callbacks, errbacks, meta, ret_value)
|
|
|
|
|
|
def full(self):
|
|
|
"""Is the pool full?
|
|
@@ -179,7 +124,7 @@ class TaskPool(object):
|
|
|
except multiprocessing.TimeoutError:
|
|
|
continue
|
|
|
else:
|
|
|
- self.on_return(ret_value, tid, callbacks, errbacks, meta)
|
|
|
+ self.on_return(tid, callbacks, errbacks, meta, ret_value)
|
|
|
processes_reaped += 1
|
|
|
return processes_reaped
|
|
|
|
|
@@ -187,14 +132,13 @@ class TaskPool(object):
|
|
|
"""Returns the process id's of all the pool workers."""
|
|
|
return [process.pid for process in self._pool._pool]
|
|
|
|
|
|
- def on_ready(self, ret_value, callbacks, errbacks, meta):
|
|
|
+ def on_ready(self, callbacks, errbacks, meta, ret_value):
|
|
|
"""What to do when a worker task is ready and its return value has
|
|
|
been collected."""
|
|
|
|
|
|
if isinstance(ret_value, ExceptionInfo):
|
|
|
if isinstance(ret_value.exception, KeyboardInterrupt) or \
|
|
|
isinstance(ret_value.exception, SystemExit):
|
|
|
- self.terminate()
|
|
|
raise ret_value.exception
|
|
|
for errback in errbacks:
|
|
|
errback(ret_value, meta)
|