blob: b96203ba10f3c6bc9aff3d46db210eb475dd1649 [file]
# Copyright 2009 Brian Quinlan. All Rights Reserved. See LICENSE file.
"""Implements ProcessPoolExecutor."""
__author__ = 'Brian Quinlan (brian@sweetapp.com)'
from futures._base import (PENDING, RUNNING, CANCELLED,
CANCELLED_AND_NOTIFIED, FINISHED,
ALL_COMPLETED,
set_future_exception, set_future_result,
Executor, Future, FutureList, ThreadEventSink)
import atexit
import Queue
import multiprocessing
import threading
import weakref
_thread_references = set()
_shutdown = False
def _python_exit():
global _shutdown
_shutdown = True
for thread_reference in _thread_references:
thread = thread_reference()
if thread is not None:
thread.join()
class _WorkItem(object):
def __init__(self, call, future, completion_tracker):
self.call = call
self.future = future
self.completion_tracker = completion_tracker
class _ResultItem(object):
def __init__(self, work_id, exception=None, result=None):
self.work_id = work_id
self.exception = exception
self.result = result
class _CallItem(object):
def __init__(self, work_id, call):
self.work_id = work_id
self.call = call
def _process_worker(call_queue, result_queue, shutdown):
while True:
try:
call_item = call_queue.get(block=True, timeout=0.1)
except Queue.Empty:
if shutdown.is_set():
return
else:
try:
r = call_item.call()
except Exception, e:
result_queue.put(_ResultItem(call_item.work_id,
exception=e))
else:
result_queue.put(_ResultItem(call_item.work_id,
result=r))
def _add_call_item_to_queue(pending_work_items,
work_ids,
call_queue):
while True:
try:
work_id = work_ids.get(block=False)
except Queue.Empty:
return
else:
work_item = pending_work_items[work_id]
if work_item.future.cancelled():
work_item.future._condition.acquire()
work_item.future._condition.notify_all()
work_item.future._condition.release()
work_item.completion_tracker.add_cancelled()
continue
else:
work_item.future._condition.acquire()
work_item.future._state = RUNNING
work_item.future._condition.release()
call_queue.put(_CallItem(work_id, work_item.call), block=True)
if call_queue.full():
return
def _result(executor_reference,
pending_work_items,
work_ids_queue,
call_queue,
result_queue,
shutdown_process_event):
while True:
_add_call_item_to_queue(pending_work_items,
work_ids_queue,
call_queue)
try:
result_item = result_queue.get(block=True, timeout=0.1)
except Queue.Empty:
executor = executor_reference()
if _shutdown or executor is None or executor._shutdown_thread:
shutdown_process_event.set()
return
del executor
else:
work_item = pending_work_items[result_item.work_id]
del pending_work_items[result_item.work_id]
if result_item.exception:
set_future_exception(work_item.future,
work_item.completion_tracker,
result_item.exception)
else:
set_future_result(work_item.future,
work_item.completion_tracker,
result_item.result)
class ProcessPoolExecutor(Executor):
def __init__(self, max_processes=None):
import warnings
warnings.warn('ProcessPoolExecutor has known deadlocking behavior')
if max_processes is None:
max_processes = multiprocessing.cpu_count()
self._max_processes = max_processes
# Make the call queue slightly larger than the number of processes to
# prevent the worker processes from starving but to make future.cancel()
# responsive.
self._call_queue = multiprocessing.Queue(self._max_processes + 1)
self._result_queue = multiprocessing.Queue()
self._work_ids = Queue.Queue()
self._queue_management_thread = None
self._processes = set()
# Shutdown is a two-step process.
self._shutdown_thread = False
self._shutdown_process_event = multiprocessing.Event()
self._shutdown_lock = threading.Lock()
self._queue_count = 0
self._pending_work_items = {}
def _adjust_process_count(self):
if self._queue_management_thread is None:
self._queue_management_thread = threading.Thread(
target=_result,
args=(weakref.ref(self),
self._pending_work_items,
self._work_ids,
self._call_queue,
self._result_queue,
self._shutdown_process_event))
self._queue_management_thread.setDaemon(True)
self._queue_management_thread.start()
_thread_references.add(weakref.ref(self._queue_management_thread))
for _ in range(len(self._processes), self._max_processes):
p = multiprocessing.Process(
target=_process_worker,
args=(self._call_queue,
self._result_queue,
self._shutdown_process_event))
p.start()
self._processes.add(p)
def run_to_futures(self, calls, timeout=None, return_when=ALL_COMPLETED):
self._shutdown_lock.acquire()
try:
if self._shutdown_thread:
raise RuntimeError('cannot run new futures after shutdown')
futures = []
event_sink = ThreadEventSink()
for index, call in enumerate(calls):
f = Future(index)
self._pending_work_items[self._queue_count] = _WorkItem(
call, f, event_sink)
self._work_ids.put(self._queue_count)
futures.append(f)
self._queue_count += 1
self._adjust_process_count()
fl = FutureList(futures, event_sink)
fl.wait(timeout=timeout, return_when=return_when)
return fl
finally:
self._shutdown_lock.release()
def shutdown(self):
self._shutdown_lock.acquire()
try:
self._shutdown_thread = True
finally:
self._shutdown_lock.release()
atexit.register(_python_exit)