Distributes tasks to a number of worker processes. New tasks can be added dynamically even after the workers have been started. Requirement: Tasks can only be added from the parent process, e.g. while consuming the results generator.
| 169 | |
| 170 | |
| 171 | class DefaultExecutionPool(ContextPool): |
| 172 | """Distributes tasks to a number of worker processes. |
| 173 | New tasks can be added dynamically even after the workers have been started. |
| 174 | Requirement: Tasks can only be added from the parent process, e.g. while |
| 175 | consuming the results generator.""" |
| 176 | |
| 177 | # Factor to calculate the maximum number of items in the work/done queue. |
| 178 | # Necessary to not overflow the queue's pipe if a keyboard interrupt happens. |
| 179 | BUFFER_FACTOR = 4 |
| 180 | |
| 181 | def __init__(self, os_context=None): |
| 182 | super(DefaultExecutionPool, self).__init__() |
| 183 | self.os_context = os_context |
| 184 | self.processes = [] |
| 185 | self.terminated = False |
| 186 | |
| 187 | # Invariant: processing_count >= #work_queue + #done_queue. It is greater |
| 188 | # when a worker takes an item from the work_queue and before the result is |
| 189 | # submitted to the done_queue. It is equal when no worker is working, |
| 190 | # e.g. when all workers have finished, and when no results are processed. |
| 191 | # Count is only accessed by the parent process. Only the parent process is |
| 192 | # allowed to remove items from the done_queue and to add items to the |
| 193 | # work_queue. |
| 194 | self.processing_count = 0 |
| 195 | |
| 196 | # Disable sigint and sigterm to prevent subprocesses from capturing the |
| 197 | # signals. |
| 198 | with without_sig(): |
| 199 | self.work_queue = Queue() |
| 200 | self.done_queue = Queue() |
| 201 | |
| 202 | def init(self, num_workers=1, heartbeat_timeout=1, notify_function=None): |
| 203 | """ |
| 204 | Args: |
| 205 | num_workers: Number of worker processes to run in parallel. |
| 206 | heartbeat_timeout: Timeout in seconds for waiting for results. Each time |
| 207 | the timeout is reached, a heartbeat is signalled and timeout is reset. |
| 208 | notify_function: Callable called to signal some events like termination. The |
| 209 | event name is passed as string. |
| 210 | """ |
| 211 | self.num_workers = num_workers |
| 212 | self.heartbeat_timeout = heartbeat_timeout |
| 213 | self.notify = notify_function or (lambda x: x) |
| 214 | |
| 215 | def add_jobs(self, jobs): |
| 216 | self.add(jobs) |
| 217 | |
| 218 | def results(self, requirement): |
| 219 | return self.imap_unordered( |
| 220 | fn=run_job, |
| 221 | gen=[], |
| 222 | process_context_fn=ProcessContext, |
| 223 | process_context_args=[requirement], |
| 224 | ) |
| 225 | |
| 226 | def imap_unordered(self, fn, gen, |
| 227 | process_context_fn=None, process_context_args=None): |
| 228 | """Maps function "fn" to items in generator "gen" on the worker processes |
no outgoing calls
searching dependent graphs…