comparison mercurial/thirdparty/concurrent/futures/thread.py @ 37623:eb687c28a915

thirdparty: vendor futures 3.2.0 Python 3 has a concurrent.futures package in the standard library for representing futures. The "futures" package on PyPI is a backport of this package to work with Python 2. The wire protocol code today has its own future concept for handling of "batch" requests. The frame-based protocol will also want to use futures. I've heavily used the "futures" package on Python 2 in other projects and it is pretty nice. It even has a built-in thread and process pool for running functions in parallel. I've used this heavily for concurrent I/O and other GIL-less activities. The existing futures API in the wire protocol code is not as nice as concurrent.futures. Since concurrent.futures is in the Python standard library and will presumably be the long-term future for futures in our code base, let's vendor the backport so we can use proper futures today. # no-check-commit because of style violations Differential Revision: https://phab.mercurial-scm.org/D3261
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 11 Apr 2018 14:48:24 -0700
parents
children 0a9c0d3480b2
comparison
equal deleted inserted replaced
37622:bfdd20d22a86 37623:eb687c28a915
1 # Copyright 2009 Brian Quinlan. All Rights Reserved.
2 # Licensed to PSF under a Contributor Agreement.
3
4 """Implements ThreadPoolExecutor."""
5
6 import atexit
7 from concurrent.futures import _base
8 import itertools
9 import Queue as queue
10 import threading
11 import weakref
12 import sys
13
14 try:
15 from multiprocessing import cpu_count
16 except ImportError:
17 # some platforms don't have multiprocessing
18 def cpu_count():
19 return None
20
21 __author__ = 'Brian Quinlan (brian@sweetapp.com)'
22
23 # Workers are created as daemon threads. This is done to allow the interpreter
24 # to exit when there are still idle threads in a ThreadPoolExecutor's thread
25 # pool (i.e. shutdown() was not called). However, allowing workers to die with
26 # the interpreter has two undesirable properties:
27 # - The workers would still be running during interpretor shutdown,
28 # meaning that they would fail in unpredictable ways.
29 # - The workers could be killed while evaluating a work item, which could
30 # be bad if the callable being evaluated has external side-effects e.g.
31 # writing to a file.
32 #
33 # To work around this problem, an exit handler is installed which tells the
34 # workers to exit when their work queues are empty and then waits until the
35 # threads finish.
36
37 _threads_queues = weakref.WeakKeyDictionary()
38 _shutdown = False
39
40 def _python_exit():
41 global _shutdown
42 _shutdown = True
43 items = list(_threads_queues.items()) if _threads_queues else ()
44 for t, q in items:
45 q.put(None)
46 for t, q in items:
47 t.join(sys.maxint)
48
49 atexit.register(_python_exit)
50
51 class _WorkItem(object):
52 def __init__(self, future, fn, args, kwargs):
53 self.future = future
54 self.fn = fn
55 self.args = args
56 self.kwargs = kwargs
57
58 def run(self):
59 if not self.future.set_running_or_notify_cancel():
60 return
61
62 try:
63 result = self.fn(*self.args, **self.kwargs)
64 except:
65 e, tb = sys.exc_info()[1:]
66 self.future.set_exception_info(e, tb)
67 else:
68 self.future.set_result(result)
69
70 def _worker(executor_reference, work_queue):
71 try:
72 while True:
73 work_item = work_queue.get(block=True)
74 if work_item is not None:
75 work_item.run()
76 # Delete references to object. See issue16284
77 del work_item
78 continue
79 executor = executor_reference()
80 # Exit if:
81 # - The interpreter is shutting down OR
82 # - The executor that owns the worker has been collected OR
83 # - The executor that owns the worker has been shutdown.
84 if _shutdown or executor is None or executor._shutdown:
85 # Notice other workers
86 work_queue.put(None)
87 return
88 del executor
89 except:
90 _base.LOGGER.critical('Exception in worker', exc_info=True)
91
92
93 class ThreadPoolExecutor(_base.Executor):
94
95 # Used to assign unique thread names when thread_name_prefix is not supplied.
96 _counter = itertools.count().next
97
98 def __init__(self, max_workers=None, thread_name_prefix=''):
99 """Initializes a new ThreadPoolExecutor instance.
100
101 Args:
102 max_workers: The maximum number of threads that can be used to
103 execute the given calls.
104 thread_name_prefix: An optional name prefix to give our threads.
105 """
106 if max_workers is None:
107 # Use this number because ThreadPoolExecutor is often
108 # used to overlap I/O instead of CPU work.
109 max_workers = (cpu_count() or 1) * 5
110 if max_workers <= 0:
111 raise ValueError("max_workers must be greater than 0")
112
113 self._max_workers = max_workers
114 self._work_queue = queue.Queue()
115 self._threads = set()
116 self._shutdown = False
117 self._shutdown_lock = threading.Lock()
118 self._thread_name_prefix = (thread_name_prefix or
119 ("ThreadPoolExecutor-%d" % self._counter()))
120
121 def submit(self, fn, *args, **kwargs):
122 with self._shutdown_lock:
123 if self._shutdown:
124 raise RuntimeError('cannot schedule new futures after shutdown')
125
126 f = _base.Future()
127 w = _WorkItem(f, fn, args, kwargs)
128
129 self._work_queue.put(w)
130 self._adjust_thread_count()
131 return f
132 submit.__doc__ = _base.Executor.submit.__doc__
133
134 def _adjust_thread_count(self):
135 # When the executor gets lost, the weakref callback will wake up
136 # the worker threads.
137 def weakref_cb(_, q=self._work_queue):
138 q.put(None)
139 # TODO(bquinlan): Should avoid creating new threads if there are more
140 # idle threads than items in the work queue.
141 num_threads = len(self._threads)
142 if num_threads < self._max_workers:
143 thread_name = '%s_%d' % (self._thread_name_prefix or self,
144 num_threads)
145 t = threading.Thread(name=thread_name, target=_worker,
146 args=(weakref.ref(self, weakref_cb),
147 self._work_queue))
148 t.daemon = True
149 t.start()
150 self._threads.add(t)
151 _threads_queues[t] = self._work_queue
152
153 def shutdown(self, wait=True):
154 with self._shutdown_lock:
155 self._shutdown = True
156 self._work_queue.put(None)
157 if wait:
158 for t in self._threads:
159 t.join(sys.maxint)
160 shutdown.__doc__ = _base.Executor.shutdown.__doc__