Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/joblib/externals/loky/process_executor.py: 19%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

604 statements  

1############################################################################### 

2# Re-implementation of the ProcessPoolExecutor more robust to faults 

3# 

4# author: Thomas Moreau and Olivier Grisel 

5# 

6# adapted from concurrent/futures/process_pool_executor.py (17/02/2017) 

7# * Add an extra management thread to detect executor_manager_thread failures, 

8# * Improve the shutdown process to avoid deadlocks, 

9# * Add timeout for workers, 

10# * More robust pickling process. 

11# 

12# Copyright 2009 Brian Quinlan. All Rights Reserved. 

13# Licensed to PSF under a Contributor Agreement. 

14 

15"""Implements ProcessPoolExecutor. 

16 

17The follow diagram and text describe the data-flow through the system: 

18 

19|======================= In-process =====================|== Out-of-process ==| 

20 

21+----------+ +----------+ +--------+ +-----------+ +---------+ 

22| | => | Work Ids | | | | Call Q | | Process | 

23| | +----------+ | | +-----------+ | Pool | 

24| | | ... | | | | ... | +---------+ 

25| | | 6 | => | | => | 5, call() | => | | 

26| | | 7 | | | | ... | | | 

27| Process | | ... | | Local | +-----------+ | Process | 

28| Pool | +----------+ | Worker | | #1..n | 

29| Executor | | Thread | | | 

30| | +----------- + | | +-----------+ | | 

31| | <=> | Work Items | <=> | | <= | Result Q | <= | | 

32| | +------------+ | | +-----------+ | | 

33| | | 6: call() | | | | ... | | | 

34| | | future | +--------+ | 4, result | | | 

35| | | ... | | 3, except | | | 

36+----------+ +------------+ +-----------+ +---------+ 

37 

38Executor.submit() called: 

39- creates a uniquely numbered _WorkItem and adds it to the "Work Items" dict 

40- adds the id of the _WorkItem to the "Work Ids" queue 

41 

42Local worker thread: 

43- reads work ids from the "Work Ids" queue and looks up the corresponding 

44 WorkItem from the "Work Items" dict: if the work item has been cancelled then 

45 it is simply removed from the dict, otherwise it is repackaged as a 

46 _CallItem and put in the "Call Q". New _CallItems are put in the "Call Q" 

47 until "Call Q" is full. NOTE: the size of the "Call Q" is kept small because 

48 calls placed in the "Call Q" can no longer be cancelled with Future.cancel(). 

49- reads _ResultItems from "Result Q", updates the future stored in the 

50 "Work Items" dict and deletes the dict entry 

51 

52Process #1..n: 

53- reads _CallItems from "Call Q", executes the calls, and puts the resulting 

54 _ResultItems in "Result Q" 

55""" 

56 

57 

58__author__ = "Thomas Moreau (thomas.moreau.2010@gmail.com)" 

59 

60 

61import faulthandler 

62import os 

63import gc 

64import sys 

65import queue 

66import struct 

67import weakref 

68import warnings 

69import itertools 

70import traceback 

71import threading 

72from time import time, sleep 

73import multiprocessing as mp 

74from functools import partial 

75from pickle import PicklingError 

76from concurrent.futures import Executor, InvalidStateError 

77from concurrent.futures._base import LOGGER 

78from concurrent.futures.process import BrokenProcessPool as _BPPException 

79from multiprocessing.connection import wait 

80 

81from ._base import Future 

82from .backend import get_context 

83from .backend.context import cpu_count, _MAX_WINDOWS_WORKERS 

84from .backend.queues import Queue, SimpleQueue 

85from .backend.reduction import set_loky_pickler, get_loky_pickler_name 

86from .backend.utils import kill_process_tree, get_exitcodes_terminated_worker 

87from .initializers import _prepare_initializer 

88 

89 

90# Mechanism to prevent infinite process spawning. When a worker of a 

91# ProcessPoolExecutor nested in MAX_DEPTH Executor tries to create a new 

92# Executor, a LokyRecursionError is raised 

93MAX_DEPTH = int(os.environ.get("LOKY_MAX_DEPTH", 10)) 

94_CURRENT_DEPTH = 0 

95 

96# Minimum time interval between two consecutive memory leak protection checks. 

97_MEMORY_LEAK_CHECK_DELAY = 1.0 

98 

99# Number of bytes of memory usage allowed over the reference process size. 

100# Workloads whose tasks differ a lot in size can legitimately exceed the default 

101# without leaking, so it is configurable; it is read in the worker processes, 

102# which inherit the environment of the process that created the executor. 

103_MAX_MEMORY_LEAK_SIZE = int( 

104 float(os.environ.get("LOKY_MAX_MEMORY_LEAK_SIZE", 3e8)) 

105) 

106 

107 

108try: 

109 from psutil import Process 

110 

111 _USE_PSUTIL = True 

112 

113 def _get_memory_usage(pid, force_gc=False): 

114 if force_gc: 

115 gc.collect() 

116 

117 mem_size = Process(pid).memory_info().rss 

118 mp.util.debug(f"psutil return memory size: {mem_size}") 

119 return mem_size 

120 

121except ImportError: 

122 _USE_PSUTIL = False 

123 

124 

125class _RecycledWorkerPid(int): 

126 """PID of a worker that the executor itself decided to shut down. 

127 

128 Behaves like the plain PID the workers usually send back, but lets the 

129 manager thread tell a recycling it triggered on purpose from a worker that 

130 stopped on its own. 

131 """ 

132 

133 

134class _ThreadWakeup: 

135 def __init__(self): 

136 self._closed = False 

137 self._reader, self._writer = mp.Pipe(duplex=False) 

138 

139 def close(self): 

140 if not self._closed: 

141 self._closed = True 

142 self._writer.close() 

143 self._reader.close() 

144 

145 def wakeup(self): 

146 if not self._closed: 

147 self._writer.send_bytes(b"") 

148 

149 def clear(self): 

150 if not self._closed: 

151 while self._reader.poll(): 

152 self._reader.recv_bytes() 

153 

154 

155class _ExecutorFlags: 

156 """necessary references to maintain executor states without preventing gc 

157 

158 It permits to keep the information needed by executor_manager_thread 

159 and crash_detection_thread to maintain the pool without preventing the 

160 garbage collection of unreferenced executors. 

161 """ 

162 

163 def __init__(self, shutdown_lock): 

164 

165 self.shutdown = False 

166 self.broken = None 

167 self.kill_workers = False 

168 self.shutdown_lock = shutdown_lock 

169 

170 def flag_as_shutting_down(self, kill_workers=None): 

171 with self.shutdown_lock: 

172 self.shutdown = True 

173 if kill_workers is not None: 

174 self.kill_workers = kill_workers 

175 

176 def flag_as_broken(self, broken): 

177 with self.shutdown_lock: 

178 self.shutdown = True 

179 self.broken = broken 

180 

181 

182# Prior to 3.9, executor_manager_thread is created as daemon thread. This means 

183# that it is not joined automatically when the interpreter is shutting down. 

184# To work around this problem, an exit handler is installed to tell the 

185# thread to exit when the interpreter is shutting down and then waits until 

186# it finishes. The thread needs to be daemonized because the atexit hooks are 

187# called after all non daemonized threads are joined. 

188# 

189# Starting 3.9, there exists a specific atexit hook to be called before joining 

190# the threads so the executor_manager_thread does not need to be daemonized 

191# anymore. 

192# 

193# The atexit hooks are registered when starting the first ProcessPoolExecutor 

194# to avoid import having an effect on the interpreter. 

195 

196_global_shutdown = False 

197_global_shutdown_lock = threading.Lock() 

198_threads_wakeups = weakref.WeakKeyDictionary() 

199 

200 

201def _python_exit(): 

202 global _global_shutdown 

203 _global_shutdown = True 

204 

205 # Materialize the list of items to avoid error due to iterating over 

206 # changing size dictionary. 

207 items = list(_threads_wakeups.items()) 

208 if len(items) > 0: 

209 mp.util.debug( 

210 f"Interpreter shutting down. Waking up {len(items)}" 

211 f"executor_manager_thread:\n{items}" 

212 ) 

213 

214 # Wake up the executor_manager_thread's so they can detect the interpreter 

215 # is shutting down and exit. 

216 for _, (shutdown_lock, thread_wakeup) in items: 

217 with shutdown_lock: 

218 thread_wakeup.wakeup() 

219 

220 # Collect the executor_manager_thread's to make sure we exit cleanly. 

221 for thread, _ in items: 

222 # This locks is to prevent situations where an executor is gc'ed in one 

223 # thread while the atexit finalizer is running in another thread. 

224 with _global_shutdown_lock: 

225 thread.join() 

226 

227 

228# With the fork context, _thread_wakeups is propagated to children. 

229# Clear it after fork to avoid some situation that can cause some 

230# freeze when joining the workers. 

231mp.util.register_after_fork(_threads_wakeups, lambda obj: obj.clear()) 

232 

233 

234# Module variable to register the at_exit call 

235process_pool_executor_at_exit = None 

236 

237# Controls how many more calls than processes will be queued in the call queue. 

238# A smaller number will mean that processes spend more time idle waiting for 

239# work while a larger number will make Future.cancel() succeed less frequently 

240# (Futures in the call queue cannot be cancelled). 

241EXTRA_QUEUED_CALLS = 1 

242 

243 

244class _RemoteTraceback(Exception): 

245 """Embed stringification of remote traceback in local traceback""" 

246 

247 def __init__(self, tb=None): 

248 self.tb = f'\n"""\n{tb}"""' 

249 

250 def __str__(self): 

251 return self.tb 

252 

253 

254# Do not inherit from BaseException to mirror 

255# concurrent.futures.process._ExceptionWithTraceback 

256class _ExceptionWithTraceback: 

257 def __init__(self, exc): 

258 tb = getattr(exc, "__traceback__", None) 

259 if tb is None: 

260 _, _, tb = sys.exc_info() 

261 tb = traceback.format_exception(type(exc), exc, tb) 

262 tb = "".join(tb) 

263 self.exc = exc 

264 self.tb = tb 

265 

266 def __reduce__(self): 

267 return _rebuild_exc, (self.exc, self.tb) 

268 

269 

270def _rebuild_exc(exc, tb): 

271 exc.__cause__ = _RemoteTraceback(tb) 

272 return exc 

273 

274 

275class _WorkItem: 

276 

277 __slots__ = ["future", "fn", "args", "kwargs"] 

278 

279 def __init__(self, future, fn, args, kwargs): 

280 self.future = future 

281 self.fn = fn 

282 self.args = args 

283 self.kwargs = kwargs 

284 

285 

286class _ResultItem: 

287 def __init__(self, work_id, exception=None, result=None): 

288 self.work_id = work_id 

289 self.exception = exception 

290 self.result = result 

291 

292 

293class _CallItem: 

294 def __init__(self, work_id, fn, args, kwargs): 

295 self.work_id = work_id 

296 self.fn = fn 

297 self.args = args 

298 self.kwargs = kwargs 

299 

300 # Store the current loky_pickler so it is correctly set in the worker 

301 self.loky_pickler = get_loky_pickler_name() 

302 

303 def __call__(self): 

304 set_loky_pickler(self.loky_pickler) 

305 return self.fn(*self.args, **self.kwargs) 

306 

307 def __repr__(self): 

308 return ( 

309 f"CallItem({self.work_id}, {self.fn}, {self.args}, {self.kwargs})" 

310 ) 

311 

312 

313class _SafeQueue(Queue): 

314 """Safe Queue set exception to the future object linked to a job""" 

315 

316 def __init__( 

317 self, 

318 max_size=0, 

319 ctx=None, 

320 pending_work_items=None, 

321 running_work_items=None, 

322 thread_wakeup=None, 

323 shutdown_lock=None, 

324 executor_flags=None, 

325 reducers=None, 

326 ): 

327 self.thread_wakeup = thread_wakeup 

328 self.shutdown_lock = shutdown_lock 

329 self.executor_flags = executor_flags 

330 self.pending_work_items = pending_work_items 

331 self.running_work_items = running_work_items 

332 super().__init__(max_size, reducers=reducers, ctx=ctx) 

333 

334 def _on_queue_feeder_error(self, e, obj): 

335 if not isinstance(obj, _CallItem): 

336 super()._on_queue_feeder_error(e, obj) 

337 return 

338 # work_item can be None if another process terminated. In this 

339 # case, the executor_manager_thread fails all work_items with 

340 # BrokenProcessPool 

341 work_item = self.pending_work_items.pop(obj.work_id, None) 

342 try: 

343 # format traceback only works on python3 

344 if isinstance(e, struct.error): 

345 raised_error = RuntimeError( 

346 "The task could not be sent to the workers as it is too " 

347 "large for `send_bytes`." 

348 ) 

349 else: 

350 raised_error = PicklingError( 

351 "Could not pickle the task to send it to the workers." 

352 ) 

353 tb = traceback.format_exception( 

354 type(e), e, getattr(e, "__traceback__", None) 

355 ) 

356 raised_error.__cause__ = _RemoteTraceback("".join(tb)) 

357 self.running_work_items.remove(obj.work_id) 

358 except BaseException as hook_exc: 

359 # An error here would kill the feeder thread silently and leave 

360 # every later task unsent: flag the executor as broken instead 

361 raised_error = BrokenProcessPool( 

362 "The call queue feeder thread crashed: the executor is " 

363 "broken and the pending tasks have been cancelled." 

364 ) 

365 tb = traceback.format_exception( 

366 type(hook_exc), hook_exc, hook_exc.__traceback__ 

367 ) 

368 raised_error.__cause__ = _RemoteTraceback("".join(tb)) 

369 LOGGER.critical( 

370 "Exception in call queue feeder error hook:", exc_info=True 

371 ) 

372 self.executor_flags.flag_as_broken(raised_error) 

373 if work_item is not None: 

374 work_item.future.set_exception(raised_error) 

375 del work_item 

376 with self.shutdown_lock: 

377 self.thread_wakeup.wakeup() 

378 

379 

380def _get_chunks(chunksize, *iterables): 

381 """Iterates over zip()ed iterables in chunks.""" 

382 it = zip(*iterables) 

383 while True: 

384 chunk = tuple(itertools.islice(it, chunksize)) 

385 if not chunk: 

386 return 

387 yield chunk 

388 

389 

390def _process_chunk(fn, chunk): 

391 """Processes a chunk of an iterable passed to map. 

392 

393 Runs the function passed to map() on a chunk of the 

394 iterable passed to map. 

395 

396 This function is run in a separate process. 

397 

398 """ 

399 return [fn(*args) for args in chunk] 

400 

401 

402def _sendback_result(result_queue, work_id, result=None, exception=None): 

403 """Safely send back the given result or exception""" 

404 try: 

405 result_queue.put( 

406 _ResultItem(work_id, result=result, exception=exception) 

407 ) 

408 except BaseException as e: 

409 exc = _ExceptionWithTraceback(e) 

410 result_queue.put(_ResultItem(work_id, exception=exc)) 

411 

412 

413def _enable_faulthandler_if_needed(): 

414 if "PYTHONFAULTHANDLER" in os.environ: 

415 # Respect the environment variable to configure faulthandler. This 

416 # makes it possible to never enable faulthandler in the loky workers by 

417 # setting PYTHONFAULTHANDLER=0 explicitly in the environment. 

418 mp.util.debug( 

419 f"faulthandler explicitly configured by environment variable: " 

420 f"PYTHONFAULTHANDLER={os.environ['PYTHONFAULTHANDLER']}." 

421 ) 

422 else: 

423 if faulthandler.is_enabled(): 

424 # Fault handler is already enabled, possibly via a custom 

425 # initializer to customize the behavior. 

426 mp.util.debug("faulthandler already enabled.") 

427 else: 

428 # Enable faulthandler by default with default paramaters otherwise. 

429 mp.util.debug( 

430 "Enabling faulthandler to report tracebacks on worker crashes." 

431 ) 

432 faulthandler.enable() 

433 

434 

435def _process_worker( 

436 call_queue, 

437 result_queue, 

438 initializer, 

439 initargs, 

440 processes_management_lock, 

441 timeout, 

442 worker_exit_lock, 

443 current_depth, 

444): 

445 """Evaluates calls from call_queue and places the results in result_queue. 

446 

447 This worker is run in a separate process. 

448 

449 Args: 

450 call_queue: A ctx.Queue of _CallItems that will be read and 

451 evaluated by the worker. 

452 result_queue: A ctx.Queue of _ResultItems that will written 

453 to by the worker. 

454 initializer: A callable initializer, or None 

455 initargs: A tuple of args for the initializer 

456 processes_management_lock: A ctx.Lock avoiding worker timeout while 

457 some workers are being spawned. 

458 timeout: maximum time to wait for a new item in the call_queue. If that 

459 time is expired, the worker will shutdown. 

460 worker_exit_lock: Lock to avoid flagging the executor as broken on 

461 workers timeout. 

462 current_depth: Nested parallelism level, to avoid infinite spawning. 

463 """ 

464 if initializer is not None: 

465 try: 

466 initializer(*initargs) 

467 except BaseException: 

468 LOGGER.critical("Exception in initializer:", exc_info=True) 

469 # The parent will notice that the process stopped and 

470 # mark the pool broken 

471 return 

472 

473 # set the global _CURRENT_DEPTH mechanism to limit recursive call 

474 global _CURRENT_DEPTH 

475 _CURRENT_DEPTH = current_depth 

476 _process_reference_size = None 

477 _last_memory_leak_check = None 

478 pid = os.getpid() 

479 

480 mp.util.debug(f"Worker started with timeout={timeout}") 

481 _enable_faulthandler_if_needed() 

482 

483 while True: 

484 try: 

485 call_item = call_queue.get(block=True, timeout=timeout) 

486 if call_item is None: 

487 mp.util.info("Shutting down worker on sentinel") 

488 except queue.Empty: 

489 mp.util.info(f"Shutting down worker after timeout {timeout:0.3f}s") 

490 if processes_management_lock.acquire(block=False): 

491 processes_management_lock.release() 

492 call_item = None 

493 else: 

494 mp.util.info("Could not acquire processes_management_lock") 

495 continue 

496 except BaseException: 

497 previous_tb = traceback.format_exc() 

498 try: 

499 result_queue.put(_RemoteTraceback(previous_tb)) 

500 except BaseException: 

501 # If we cannot format correctly the exception, at least print 

502 # the traceback. 

503 print(previous_tb) 

504 mp.util.debug("Exiting with code 1") 

505 sys.exit(1) 

506 if call_item is None: 

507 # Notify queue management thread about worker shutdown 

508 result_queue.put(pid) 

509 is_clean = worker_exit_lock.acquire(True, timeout=30) 

510 

511 # Early notify any loky executor running in this worker process 

512 # (nested parallelism) that this process is about to shutdown to 

513 # avoid a deadlock waiting undifinitely for the worker to finish. 

514 _python_exit() 

515 

516 if is_clean: 

517 mp.util.debug("Exited cleanly") 

518 else: 

519 mp.util.info("Main process did not release worker_exit") 

520 return 

521 try: 

522 r = call_item() 

523 except BaseException as e: 

524 exc = _ExceptionWithTraceback(e) 

525 result_queue.put(_ResultItem(call_item.work_id, exception=exc)) 

526 else: 

527 _sendback_result(result_queue, call_item.work_id, result=r) 

528 del r 

529 

530 # Free the resource as soon as possible, to avoid holding onto 

531 # open files or shared memory that is not needed anymore 

532 del call_item 

533 

534 if _USE_PSUTIL: 

535 if _process_reference_size is None: 

536 # Make reference measurement after the first call 

537 _process_reference_size = _get_memory_usage(pid, force_gc=True) 

538 _last_memory_leak_check = time() 

539 continue 

540 if time() - _last_memory_leak_check > _MEMORY_LEAK_CHECK_DELAY: 

541 mem_usage = _get_memory_usage(pid) 

542 _last_memory_leak_check = time() 

543 if mem_usage - _process_reference_size < _MAX_MEMORY_LEAK_SIZE: 

544 # Memory usage stays within bounds: everything is fine. 

545 continue 

546 

547 # Check again memory usage; this time take the measurement 

548 # after a forced garbage collection to break any reference 

549 # cycles. 

550 mem_usage = _get_memory_usage(pid, force_gc=True) 

551 _last_memory_leak_check = time() 

552 if mem_usage - _process_reference_size < _MAX_MEMORY_LEAK_SIZE: 

553 # The GC managed to free the memory: everything is fine. 

554 continue 

555 

556 # The process is leaking memory: let the master process 

557 # know that we need to start a new worker. 

558 mp.util.info("Memory leak detected: shutting down worker") 

559 result_queue.put(_RecycledWorkerPid(pid)) 

560 with worker_exit_lock: 

561 mp.util.debug("Exit due to memory leak") 

562 return 

563 else: 

564 # if psutil is not installed, trigger gc.collect events 

565 # regularly to limit potential memory leaks due to reference cycles 

566 if _last_memory_leak_check is None or ( 

567 time() - _last_memory_leak_check > _MEMORY_LEAK_CHECK_DELAY 

568 ): 

569 gc.collect() 

570 _last_memory_leak_check = time() 

571 

572 

573class _ExecutorManagerThread(threading.Thread): 

574 """Manages the communication between this process and the worker processes. 

575 

576 The manager is run in a local thread. 

577 

578 Args: 

579 executor: A reference to the ProcessPoolExecutor that owns 

580 this thread. A weakref will be own by the manager as well as 

581 references to internal objects used to introspect the state of 

582 the executor. 

583 """ 

584 

585 def __init__(self, executor): 

586 # Store references to necessary internals of the executor. 

587 

588 # A _ThreadWakeup to allow waking up the executor_manager_thread from 

589 # the main Thread and avoid deadlocks caused by permanently 

590 # locked queues. 

591 self.thread_wakeup = executor._executor_manager_thread_wakeup 

592 self.shutdown_lock = executor._shutdown_lock 

593 

594 # A weakref.ref to the ProcessPoolExecutor that owns this thread. Used 

595 # to determine if the ProcessPoolExecutor has been garbage collected 

596 # and that the manager can exit. 

597 # When the executor gets garbage collected, the weakref callback 

598 # will wake up the queue management thread so that it can terminate 

599 # if there is no pending work item. 

600 def weakref_cb( 

601 _, 

602 thread_wakeup=self.thread_wakeup, 

603 shutdown_lock=self.shutdown_lock, 

604 ): 

605 if mp is not None: 

606 # At this point, the multiprocessing module can already be 

607 # garbage collected. We only log debug info when still 

608 # possible. 

609 mp.util.debug( 

610 "Executor collected: triggering callback for" 

611 " QueueManager wakeup" 

612 ) 

613 with shutdown_lock: 

614 thread_wakeup.wakeup() 

615 

616 self.executor_reference = weakref.ref(executor, weakref_cb) 

617 

618 # The flags of the executor 

619 self.executor_flags = executor._flags 

620 

621 # A list of the ctx.Process instances used as workers. 

622 self.processes = executor._processes 

623 

624 # A ctx.Queue that will be filled with _CallItems derived from 

625 # _WorkItems for processing by the process workers. 

626 self.call_queue = executor._call_queue 

627 

628 # A ctx.SimpleQueue of _ResultItems generated by the process workers. 

629 self.result_queue = executor._result_queue 

630 

631 # A queue.Queue of work ids e.g. Queue([5, 6, ...]). 

632 self.work_ids_queue = executor._work_ids 

633 

634 # A dict mapping work ids to _WorkItems e.g. 

635 # {5: <_WorkItem...>, 6: <_WorkItem...>, ...} 

636 self.pending_work_items = executor._pending_work_items 

637 

638 # A list of the work_ids that are currently running 

639 self.running_work_items = executor._running_work_items 

640 

641 # A lock to avoid concurrent shutdown of workers on timeout and spawn 

642 # of new processes or shut down 

643 self.processes_management_lock = executor._processes_management_lock 

644 

645 # Long-running executors can recycle workers many times over their 

646 # lifetime; the user only needs to hear about it once. 

647 self.recycling_warned = False 

648 

649 super().__init__(name="ExecutorManagerThread") 

650 if sys.version_info < (3, 9): 

651 self.daemon = True 

652 

653 def run(self): 

654 # Main loop for the executor manager thread. 

655 try: 

656 self._run_loop() 

657 except BaseException as exc: 

658 # Nothing in the loop is expected to raise, so this is a bug, a 

659 # warning filter turning one of the warnings below into an error, 

660 # or a failure of the runtime itself, such as a queue that cannot 

661 # start its feeder thread while the interpreter is shutting down 

662 # (python/cpython#109047). Letting the exception kill this thread 

663 # would leave every pending future unresolved and the executor 

664 # looking healthy, so a caller waiting on a result would hang 

665 # forever. 

666 # Flag the executor as broken instead, with the error as cause. 

667 bpe = BrokenProcessPool( 

668 "The executor manager thread crashed: the executor is broken " 

669 "and the pending tasks have been cancelled." 

670 ) 

671 tb = traceback.format_exception(type(exc), exc, exc.__traceback__) 

672 bpe.__cause__ = _RemoteTraceback("".join(tb)) 

673 # Also log it: with no pending future, nothing else reports it 

674 LOGGER.critical( 

675 "Exception in executor manager thread:", exc_info=True 

676 ) 

677 self.terminate_broken(bpe) 

678 

679 def _run_loop(self): 

680 while True: 

681 self.add_call_item_to_queue() 

682 

683 result_item, is_broken, bpe = self.wait_result_broken_or_wakeup() 

684 

685 # The call queue feeder thread flags the executor broken and 

686 # wakes us up when it cannot report a task failure itself 

687 if not is_broken and self.executor_flags.broken is not None: 

688 is_broken, bpe = True, self.executor_flags.broken 

689 if is_broken: 

690 self.terminate_broken(bpe) 

691 return 

692 if result_item is not None: 

693 self.process_result_item(result_item) 

694 # Delete reference to result_item to avoid keeping references 

695 # while waiting on new results. 

696 del result_item 

697 

698 if self.is_shutting_down(): 

699 self.flag_executor_shutting_down() 

700 

701 # When only canceled futures remain in pending_work_items, our 

702 # next call to wait_result_broken_or_wakeup would hang forever. 

703 # This makes sure we have some running futures or none at all. 

704 self.add_call_item_to_queue() 

705 

706 # Since no new work items can be added, it is safe to shutdown 

707 # this thread if there are no pending work items. 

708 if not self.pending_work_items: 

709 self.join_executor_internals() 

710 return 

711 

712 def add_call_item_to_queue(self): 

713 # Fills call_queue with _WorkItems from pending_work_items. 

714 # This function never blocks. 

715 while True: 

716 if self.call_queue.full(): 

717 return 

718 try: 

719 work_id = self.work_ids_queue.get(block=False) 

720 except queue.Empty: 

721 return 

722 else: 

723 work_item = self.pending_work_items[work_id] 

724 

725 if work_item.future.set_running_or_notify_cancel(): 

726 self.running_work_items += [work_id] 

727 self.call_queue.put( 

728 _CallItem( 

729 work_id, 

730 work_item.fn, 

731 work_item.args, 

732 work_item.kwargs, 

733 ), 

734 block=True, 

735 ) 

736 else: 

737 del self.pending_work_items[work_id] 

738 continue 

739 

740 def wait_result_broken_or_wakeup(self): 

741 # Wait for a result to be ready in the result_queue while checking 

742 # that all worker processes are still running, or for a wake up 

743 # signal send. The wake up signals come either from new tasks being 

744 # submitted, from the executor being shutdown/gc-ed, or from the 

745 # shutdown of the python interpreter. 

746 result_reader = self.result_queue._reader 

747 wakeup_reader = self.thread_wakeup._reader 

748 readers = [result_reader, wakeup_reader] 

749 worker_sentinels = [p.sentinel for p in list(self.processes.values())] 

750 ready = wait(readers + worker_sentinels) 

751 

752 bpe = None 

753 is_broken = True 

754 result_item = None 

755 if result_reader in ready: 

756 try: 

757 result_item = result_reader.recv() 

758 if isinstance(result_item, _RemoteTraceback): 

759 bpe = BrokenProcessPool( 

760 "A task has failed to un-serialize. Please ensure that" 

761 " the arguments of the function are all picklable." 

762 ) 

763 bpe.__cause__ = result_item 

764 else: 

765 is_broken = False 

766 except BaseException as e: 

767 bpe = BrokenProcessPool( 

768 "A result has failed to un-serialize. Please ensure that " 

769 "the objects returned by the function are always " 

770 "picklable." 

771 ) 

772 tb = traceback.format_exception( 

773 type(e), e, getattr(e, "__traceback__", None) 

774 ) 

775 bpe.__cause__ = _RemoteTraceback("".join(tb)) 

776 

777 elif wakeup_reader in ready: 

778 # This is simply a wake-up event that might either trigger putting 

779 # more tasks in the queue or trigger the clean up of resources. 

780 is_broken = False 

781 else: 

782 # A worker has terminated and we don't know why, set the state of 

783 # the executor as broken 

784 exit_codes = "" 

785 if sys.platform != "win32": 

786 # In Windows, introspecting terminated workers exitcodes seems 

787 # unstable, therefore they are not appended in the exception 

788 # message. 

789 exit_codes = ( 

790 "\nThe exit codes of the workers are " 

791 f"{get_exitcodes_terminated_worker(self.processes)}" 

792 ) 

793 mp.util.debug( 

794 "A worker unexpectedly terminated. Workers that " 

795 "might have caused the breakage: " 

796 + str( 

797 { 

798 p.name: p.exitcode 

799 for p in list(self.processes.values()) 

800 if p is not None and p.sentinel in ready 

801 } 

802 ) 

803 ) 

804 bpe = TerminatedWorkerError( 

805 "A worker process managed by the executor was unexpectedly " 

806 "terminated. This could be caused by a segmentation fault " 

807 "while calling the function or by an excessive memory usage " 

808 "causing the Operating System to kill the worker.\n" 

809 f"{exit_codes}\n" 

810 "Detailed tracebacks of the workers should have been printed " 

811 "to stderr in the executor process if faulthandler was not " 

812 "disabled." 

813 ) 

814 

815 self.thread_wakeup.clear() 

816 

817 return result_item, is_broken, bpe 

818 

819 def process_result_item(self, result_item): 

820 # Process the received a result_item. This can be either the PID of a 

821 # worker that exited gracefully or a _ResultItem 

822 

823 if isinstance(result_item, int): 

824 # Clean shutdown of a worker using its PID, either on request 

825 # by the executor.shutdown method or by the timeout of the worker 

826 # itself: we should not mark the executor as broken. 

827 with self.processes_management_lock: 

828 p = self.processes.pop(result_item, None) 

829 

830 # p can be None if the executor is concurrently shutting down. 

831 if p is not None: 

832 p._worker_exit_lock.release() 

833 mp.util.debug( 

834 f"joining {p.name} when processing {p.pid} as result_item" 

835 ) 

836 p.join() 

837 del p 

838 

839 # Make sure the executor have the right number of worker, even if a 

840 # worker timeout while some jobs were submitted. If some work is 

841 # pending or there is less processes than running items, we need to 

842 # start a new Process and raise a warning. 

843 n_pending = len(self.pending_work_items) 

844 n_running = len(self.running_work_items) 

845 if n_pending - n_running > 0 or n_running > len(self.processes): 

846 executor = self.executor_reference() 

847 if ( 

848 executor is not None 

849 and len(self.processes) < executor._max_workers 

850 ): 

851 with executor._processes_management_lock: 

852 executor._adjust_process_count() 

853 executor = None 

854 # Warn only once the pool is back to full strength, so 

855 # that a filter turning this into an error costs no worker 

856 if not isinstance(result_item, _RecycledWorkerPid): 

857 warnings.warn( 

858 "A worker stopped while some jobs were given to " 

859 "the executor. This can be caused by a too short " 

860 "worker timeout or by a memory leak triggered by the " 

861 "OS-level out-of-memory (OOM) process killer.", 

862 UserWarning, 

863 ) 

864 elif not self.recycling_warned: 

865 # Recycling is a deliberate decision of the executor and 

866 # it is transparent to the caller, so repeating it for 

867 # every worker of a long-running executor is just noise. 

868 self.recycling_warned = True 

869 warnings.warn( 

870 "A worker was restarted while some jobs were given " 

871 "to the executor because it was using more than " 

872 f"{_MAX_MEMORY_LEAK_SIZE / 1e6:.0f} MB more memory " 

873 "than when it started, which usually indicates a " 

874 "memory leak. Further restarts of this executor " 

875 "will not be reported.", 

876 UserWarning, 

877 ) 

878 else: 

879 # Received a _ResultItem so mark the future as completed. 

880 work_item = self.pending_work_items.pop(result_item.work_id, None) 

881 # work_item can be None if another process terminated (see above) 

882 if work_item is not None: 

883 if result_item.exception is not None: 

884 work_item.future.set_exception(result_item.exception) 

885 else: 

886 work_item.future.set_result(result_item.result) 

887 self.running_work_items.remove(result_item.work_id) 

888 

889 def is_shutting_down(self): 

890 # Check whether we should start shutting down the executor. 

891 executor = self.executor_reference() 

892 # No more work items can be added if: 

893 # - The interpreter is shutting down OR 

894 # - The executor that owns this thread is not broken AND 

895 # * The executor that owns this worker has been collected OR 

896 # * The executor that owns this worker has been shutdown. 

897 # If the executor is broken, it should be detected in the next loop. 

898 return _global_shutdown or ( 

899 (executor is None or self.executor_flags.shutdown) 

900 and not self.executor_flags.broken 

901 ) 

902 

903 def terminate_broken(self, bpe): 

904 # Terminate the executor because it is in a broken state. The bpe 

905 # argument can be used to display more information on the error that 

906 # lead the executor into becoming broken. 

907 

908 # Mark the process pool broken so that submits fail right now. 

909 self.executor_flags.flag_as_broken(bpe) 

910 

911 # Mark pending tasks as failed. 

912 for work_item in self.pending_work_items.values(): 

913 try: 

914 work_item.future.set_exception(bpe) 

915 except InvalidStateError: 

916 # set_exception() fails if the future is cancelled: ignore it. 

917 # Trying to check if the future is cancelled before calling 

918 # set_exception() would leave a race condition if the future is 

919 # cancelled between the check and set_exception(). 

920 pass 

921 # Delete references to object. See issue16284 

922 del work_item 

923 self.pending_work_items.clear() 

924 

925 # Terminate remaining workers forcibly: the queues or their 

926 # locks may be in a dirty state and block forever. 

927 self.kill_workers(reason="broken executor") 

928 

929 # clean up resources 

930 self.join_executor_internals() 

931 

932 def flag_executor_shutting_down(self): 

933 # Flag the executor as shutting down and cancel remaining tasks if 

934 # requested as early as possible if it is not gc-ed yet. 

935 self.executor_flags.flag_as_shutting_down() 

936 

937 # Cancel pending work items if requested. 

938 if self.executor_flags.kill_workers: 

939 # Drain the queued ids first: add_call_item_to_queue runs again 

940 # right after this and looks each of them up in pending_work_items 

941 while True: 

942 try: 

943 self.work_ids_queue.get(block=False) 

944 except queue.Empty: 

945 break 

946 while self.pending_work_items: 

947 _, work_item = self.pending_work_items.popitem() 

948 work_item.future.set_exception( 

949 ShutdownExecutorError( 

950 "The Executor was shutdown with `kill_workers=True` " 

951 "before this job could complete." 

952 ) 

953 ) 

954 del work_item 

955 

956 # Kill the remaining worker forcibly to no waste time joining them 

957 self.kill_workers(reason="executor shutting down") 

958 

959 def kill_workers(self, reason=""): 

960 # Terminate the remaining workers using SIGKILL. This function also 

961 # terminates descendant workers of the children in case there is some 

962 # nested parallelism. 

963 while self.processes: 

964 _, p = self.processes.popitem() 

965 mp.util.debug(f"terminate process {p.name}, reason: {reason}") 

966 try: 

967 kill_process_tree(p) 

968 except ProcessLookupError: # pragma: no cover 

969 pass 

970 

971 def shutdown_workers(self): 

972 # shutdown all workers in self.processes 

973 

974 # Create a list to avoid RuntimeError due to concurrent modification of 

975 # processes. nb_children_alive is thus an upper bound. Also release the 

976 # processes' _worker_exit_lock to accelerate the shutdown procedure, as 

977 # there is no need for hand-shake here. 

978 with self.processes_management_lock: 

979 n_children_to_stop = 0 

980 for p in list(self.processes.values()): 

981 mp.util.debug(f"releasing worker exit lock on {p.name}") 

982 p._worker_exit_lock.release() 

983 n_children_to_stop += 1 

984 

985 mp.util.debug(f"found {n_children_to_stop} processes to stop") 

986 

987 # Send the right number of sentinels, to make sure all children are 

988 # properly terminated. Do it with a mechanism that avoid hanging on 

989 # Full queue when all workers have already been shutdown. 

990 n_sentinels_sent = 0 

991 cooldown_time = 0.001 

992 while ( 

993 n_sentinels_sent < n_children_to_stop 

994 and self.get_n_children_alive() > 0 

995 ): 

996 for _ in range(n_children_to_stop - n_sentinels_sent): 

997 try: 

998 self.call_queue.put_nowait(None) 

999 n_sentinels_sent += 1 

1000 except queue.Full as e: 

1001 if cooldown_time > 5.0: 

1002 mp.util.info( 

1003 "failed to send all sentinels and exit with error." 

1004 f"\ncall_queue size={self.call_queue._maxsize}; " 

1005 f" full is {self.call_queue.full()}; " 

1006 ) 

1007 raise e 

1008 mp.util.info( 

1009 "full call_queue prevented to send all sentinels at " 

1010 "once, waiting..." 

1011 ) 

1012 sleep(cooldown_time) 

1013 cooldown_time *= 1.2 

1014 break 

1015 

1016 mp.util.debug(f"sent {n_sentinels_sent} sentinels to the call queue") 

1017 

1018 def join_executor_internals(self): 

1019 self.shutdown_workers() 

1020 

1021 # Release the queue's resources as soon as possible. Flag the feeder 

1022 # thread for clean exit to avoid having the crash detection thread flag 

1023 # the Executor as broken during the shutdown. This is safe as either: 

1024 # * We don't need to communicate with the workers anymore 

1025 # * There is nothing left in the Queue buffer except None sentinels 

1026 mp.util.debug("closing call_queue") 

1027 self.call_queue.close() 

1028 self.call_queue.join_thread() 

1029 

1030 # Closing result_queue 

1031 mp.util.debug("closing result_queue") 

1032 self.result_queue.close() 

1033 

1034 mp.util.debug("closing thread_wakeup") 

1035 with self.shutdown_lock: 

1036 self.thread_wakeup.close() 

1037 

1038 # If .join() is not called on the created processes then 

1039 # some ctx.Queue methods may deadlock on macOS. 

1040 with self.processes_management_lock: 

1041 mp.util.debug(f"joining {len(self.processes)} processes") 

1042 n_joined_processes = 0 

1043 while True: 

1044 try: 

1045 pid, p = self.processes.popitem() 

1046 mp.util.debug(f"joining process {p.name} with pid {pid}") 

1047 p.join() 

1048 n_joined_processes += 1 

1049 except KeyError: 

1050 break 

1051 

1052 mp.util.debug( 

1053 "executor management thread clean shutdown of " 

1054 f"{n_joined_processes} workers" 

1055 ) 

1056 

1057 def get_n_children_alive(self): 

1058 # This is an upper bound on the number of children alive. 

1059 with self.processes_management_lock: 

1060 return sum(p.is_alive() for p in list(self.processes.values())) 

1061 

1062 

1063_system_limits_checked = False 

1064_system_limited = None 

1065 

1066 

1067def _check_system_limits(): 

1068 global _system_limits_checked, _system_limited 

1069 if _system_limits_checked and _system_limited: 

1070 raise NotImplementedError(_system_limited) 

1071 _system_limits_checked = True 

1072 try: 

1073 nsems_max = os.sysconf("SC_SEM_NSEMS_MAX") 

1074 except (AttributeError, ValueError): 

1075 # sysconf not available or setting not available 

1076 return 

1077 if nsems_max == -1: 

1078 # undetermined limit, assume that limit is determined 

1079 # by available memory only 

1080 return 

1081 if nsems_max >= 256: 

1082 # minimum number of semaphores available 

1083 # according to POSIX 

1084 return 

1085 _system_limited = ( 

1086 f"system provides too few semaphores ({nsems_max} available, " 

1087 "256 necessary)" 

1088 ) 

1089 raise NotImplementedError(_system_limited) 

1090 

1091 

1092def _chain_from_iterable_of_lists(iterable): 

1093 """ 

1094 Specialized implementation of itertools.chain.from_iterable. 

1095 Each item in *iterable* should be a list. This function is 

1096 careful not to keep references to yielded objects. 

1097 """ 

1098 for element in iterable: 

1099 element.reverse() 

1100 while element: 

1101 yield element.pop() 

1102 

1103 

1104def _check_max_depth(context): 

1105 # Limit the maxmal recursion level 

1106 global _CURRENT_DEPTH 

1107 if context.get_start_method() == "fork" and _CURRENT_DEPTH > 0: 

1108 raise LokyRecursionError( 

1109 "Could not spawn extra nested processes at depth superior to " 

1110 "MAX_DEPTH=1. It is not possible to increase this limit when " 

1111 "using the 'fork' start method." 

1112 ) 

1113 

1114 if 0 < MAX_DEPTH and _CURRENT_DEPTH + 1 > MAX_DEPTH: 

1115 raise LokyRecursionError( 

1116 "Could not spawn extra nested processes at depth superior to " 

1117 f"MAX_DEPTH={MAX_DEPTH}. If this is intendend, you can change " 

1118 "this limit with the LOKY_MAX_DEPTH environment variable." 

1119 ) 

1120 

1121 

1122class LokyRecursionError(RuntimeError): 

1123 """A process tries to spawn too many levels of nested processes.""" 

1124 

1125 

1126class BrokenProcessPool(_BPPException): 

1127 """ 

1128 Raised when the executor is broken while a future was in the running state. 

1129 The cause can an error raised when unpickling the task in the worker 

1130 process or when unpickling the result value in the parent process. It can 

1131 also be caused by a worker process being terminated unexpectedly. 

1132 """ 

1133 

1134 

1135class TerminatedWorkerError(BrokenProcessPool): 

1136 """ 

1137 Raised when a process in a ProcessPoolExecutor terminated abruptly 

1138 while a future was in the running state. 

1139 """ 

1140 

1141 

1142# Alias for backward compat (for code written for loky 1.1.4 and earlier). Do 

1143# not use in new code. 

1144BrokenExecutor = BrokenProcessPool 

1145 

1146 

1147class ShutdownExecutorError(RuntimeError): 

1148 """ 

1149 Raised when a ProcessPoolExecutor is shutdown while a future was in the 

1150 running or pending state. 

1151 """ 

1152 

1153 

1154class ProcessPoolExecutor(Executor): 

1155 

1156 _at_exit = None 

1157 

1158 def __init__( 

1159 self, 

1160 max_workers=None, 

1161 job_reducers=None, 

1162 result_reducers=None, 

1163 timeout=None, 

1164 context=None, 

1165 initializer=None, 

1166 initargs=(), 

1167 env=None, 

1168 ): 

1169 """Initializes a new ProcessPoolExecutor instance. 

1170 

1171 Args: 

1172 max_workers: int, optional (default: cpu_count()) 

1173 The maximum number of processes that can be used to execute the 

1174 given calls. If None or not given then as many worker processes 

1175 will be created as the number of CPUs the current process 

1176 can use. 

1177 job_reducers, result_reducers: dict(type: reducer_func) 

1178 Custom reducer for pickling the jobs and the results from the 

1179 Executor. If only `job_reducers` is provided, `result_reducer` 

1180 will use the same reducers 

1181 timeout: int, optional (default: None) 

1182 Idle workers exit after timeout seconds. If a new job is 

1183 submitted after the timeout, the executor will start enough 

1184 new Python processes to make sure the pool of workers is full. 

1185 context: A multiprocessing context to launch the workers. This 

1186 object should provide SimpleQueue, Queue and Process. 

1187 initializer: An callable used to initialize worker processes. 

1188 initargs: A tuple of arguments to pass to the initializer. 

1189 env: A dict of environment variable to overwrite in the child 

1190 process. The environment variables are set before any module is 

1191 loaded. Note that this only works with the loky context. 

1192 """ 

1193 _check_system_limits() 

1194 

1195 if max_workers is None: 

1196 self._max_workers = cpu_count() 

1197 else: 

1198 if max_workers <= 0: 

1199 raise ValueError("max_workers must be greater than 0") 

1200 self._max_workers = max_workers 

1201 

1202 if ( 

1203 sys.platform == "win32" 

1204 and self._max_workers > _MAX_WINDOWS_WORKERS 

1205 ): 

1206 warnings.warn( 

1207 f"On Windows, max_workers cannot exceed {_MAX_WINDOWS_WORKERS} " 

1208 "due to limitations of the operating system." 

1209 ) 

1210 self._max_workers = _MAX_WINDOWS_WORKERS 

1211 

1212 if context is None: 

1213 context = get_context() 

1214 self._context = context 

1215 self._env = env 

1216 

1217 self._initializer, self._initargs = _prepare_initializer( 

1218 initializer, initargs 

1219 ) 

1220 _check_max_depth(self._context) 

1221 

1222 if result_reducers is None: 

1223 result_reducers = job_reducers 

1224 

1225 # Timeout 

1226 self._timeout = timeout 

1227 

1228 # Management thread 

1229 self._executor_manager_thread = None 

1230 

1231 # Map of pids to processes 

1232 self._processes = {} 

1233 

1234 # Internal variables of the ProcessPoolExecutor 

1235 self._processes = {} 

1236 self._queue_count = 0 

1237 self._pending_work_items = {} 

1238 self._running_work_items = [] 

1239 self._work_ids = queue.Queue() 

1240 self._processes_management_lock = self._context.Lock() 

1241 self._executor_manager_thread = None 

1242 self._shutdown_lock = threading.Lock() 

1243 

1244 # _ThreadWakeup is a communication channel used to interrupt the wait 

1245 # of the main loop of executor_manager_thread from another thread (e.g. 

1246 # when calling executor.submit or executor.shutdown). We do not use the 

1247 # _result_queue to send wakeup signals to the executor_manager_thread 

1248 # as it could result in a deadlock if a worker process dies with the 

1249 # _result_queue write lock still acquired. 

1250 # 

1251 # _shutdown_lock must be locked to access _ThreadWakeup.wakeup. 

1252 self._executor_manager_thread_wakeup = _ThreadWakeup() 

1253 

1254 # Flag to hold the state of the Executor. This permits to introspect 

1255 # the Executor state even once it has been garbage collected. 

1256 self._flags = _ExecutorFlags(self._shutdown_lock) 

1257 

1258 # Finally setup the queues for interprocess communication 

1259 self._setup_queues(job_reducers, result_reducers) 

1260 

1261 mp.util.debug("ProcessPoolExecutor is setup") 

1262 

1263 def _setup_queues(self, job_reducers, result_reducers, queue_size=None): 

1264 # Make the call queue slightly larger than the number of processes to 

1265 # prevent the worker processes from idling. But don't make it too big 

1266 # because futures in the call queue cannot be cancelled. 

1267 if queue_size is None: 

1268 queue_size = 2 * self._max_workers + EXTRA_QUEUED_CALLS 

1269 self._call_queue = _SafeQueue( 

1270 max_size=queue_size, 

1271 pending_work_items=self._pending_work_items, 

1272 running_work_items=self._running_work_items, 

1273 thread_wakeup=self._executor_manager_thread_wakeup, 

1274 shutdown_lock=self._shutdown_lock, 

1275 executor_flags=self._flags, 

1276 reducers=job_reducers, 

1277 ctx=self._context, 

1278 ) 

1279 # Killed worker processes can produce spurious "broken pipe" 

1280 # tracebacks in the queue's own worker thread. But we detect killed 

1281 # processes anyway, so silence the tracebacks. 

1282 self._call_queue._ignore_epipe = True 

1283 

1284 self._result_queue = SimpleQueue( 

1285 reducers=result_reducers, ctx=self._context 

1286 ) 

1287 

1288 def _start_executor_manager_thread(self): 

1289 if self._executor_manager_thread is None: 

1290 mp.util.debug("_start_executor_manager_thread called") 

1291 

1292 # Start the processes so that their sentinels are known. 

1293 self._executor_manager_thread = _ExecutorManagerThread(self) 

1294 self._executor_manager_thread.start() 

1295 

1296 # register this executor in a mechanism that ensures it will wakeup 

1297 # when the interpreter is exiting. 

1298 _threads_wakeups[self._executor_manager_thread] = ( 

1299 self._shutdown_lock, 

1300 self._executor_manager_thread_wakeup, 

1301 ) 

1302 

1303 global process_pool_executor_at_exit 

1304 if process_pool_executor_at_exit is None: 

1305 # Ensure that the _python_exit function will be called before 

1306 # the multiprocessing.Queue._close finalizers which have an 

1307 # exitpriority of 10. 

1308 

1309 if sys.version_info < (3, 9): 

1310 process_pool_executor_at_exit = mp.util.Finalize( 

1311 None, _python_exit, exitpriority=20 

1312 ) 

1313 else: 

1314 process_pool_executor_at_exit = threading._register_atexit( 

1315 _python_exit 

1316 ) 

1317 

1318 def _adjust_process_count(self): 

1319 while len(self._processes) < self._max_workers: 

1320 worker_exit_lock = self._context.BoundedSemaphore(1) 

1321 args = ( 

1322 self._call_queue, 

1323 self._result_queue, 

1324 self._initializer, 

1325 self._initargs, 

1326 self._processes_management_lock, 

1327 self._timeout, 

1328 worker_exit_lock, 

1329 _CURRENT_DEPTH + 1, 

1330 ) 

1331 worker_exit_lock.acquire() 

1332 try: 

1333 # Try to spawn the process with some environment variable to 

1334 # overwrite but it only works with the loky context for now. 

1335 p = self._context.Process( 

1336 target=_process_worker, args=args, env=self._env 

1337 ) 

1338 except TypeError: 

1339 p = self._context.Process(target=_process_worker, args=args) 

1340 p._worker_exit_lock = worker_exit_lock 

1341 p.start() 

1342 self._processes[p.pid] = p 

1343 mp.util.debug( 

1344 f"Adjusted process count to {self._max_workers}: " 

1345 f"{[(p.name, pid) for pid, p in self._processes.items()]}" 

1346 ) 

1347 

1348 def _ensure_executor_running(self): 

1349 """ensures all workers and management thread are running""" 

1350 with self._processes_management_lock: 

1351 if len(self._processes) != self._max_workers: 

1352 self._adjust_process_count() 

1353 self._start_executor_manager_thread() 

1354 

1355 def submit(self, fn, *args, **kwargs): 

1356 with self._flags.shutdown_lock: 

1357 if self._flags.broken is not None: 

1358 raise self._flags.broken 

1359 if self._flags.shutdown: 

1360 raise ShutdownExecutorError( 

1361 "cannot schedule new futures after shutdown" 

1362 ) 

1363 

1364 # Cannot submit a new calls once the interpreter is shutting down. 

1365 # This check avoids spawning new processes at exit. 

1366 if _global_shutdown: 

1367 raise RuntimeError( 

1368 "cannot schedule new futures after interpreter shutdown" 

1369 ) 

1370 

1371 f = Future() 

1372 w = _WorkItem(f, fn, args, kwargs) 

1373 

1374 self._pending_work_items[self._queue_count] = w 

1375 self._work_ids.put(self._queue_count) 

1376 self._queue_count += 1 

1377 # Wake up queue management thread 

1378 self._executor_manager_thread_wakeup.wakeup() 

1379 

1380 self._ensure_executor_running() 

1381 return f 

1382 

1383 submit.__doc__ = Executor.submit.__doc__ 

1384 

1385 def map(self, fn, *iterables, **kwargs): 

1386 """Returns an iterator equivalent to map(fn, iter). 

1387 

1388 Args: 

1389 fn: A callable that will take as many arguments as there are 

1390 passed iterables. 

1391 timeout: The maximum number of seconds to wait. If None, then there 

1392 is no limit on the wait time. 

1393 chunksize: If greater than one, the iterables will be chopped into 

1394 chunks of size chunksize and submitted to the process pool. 

1395 If set to one, the items in the list will be sent one at a 

1396 time. 

1397 

1398 Returns: 

1399 An iterator equivalent to: map(func, *iterables) but the calls may 

1400 be evaluated out-of-order. 

1401 

1402 Raises: 

1403 TimeoutError: If the entire result iterator could not be generated 

1404 before the given timeout. 

1405 Exception: If fn(*args) raises for any values. 

1406 """ 

1407 timeout = kwargs.get("timeout", None) 

1408 chunksize = kwargs.get("chunksize", 1) 

1409 if chunksize < 1: 

1410 raise ValueError("chunksize must be >= 1.") 

1411 

1412 results = super().map( 

1413 partial(_process_chunk, fn), 

1414 _get_chunks(chunksize, *iterables), 

1415 timeout=timeout, 

1416 ) 

1417 return _chain_from_iterable_of_lists(results) 

1418 

1419 def shutdown(self, wait=True, kill_workers=False): 

1420 mp.util.debug(f"shutting down executor {self}") 

1421 

1422 self._flags.flag_as_shutting_down(kill_workers) 

1423 executor_manager_thread = self._executor_manager_thread 

1424 executor_manager_thread_wakeup = self._executor_manager_thread_wakeup 

1425 

1426 if executor_manager_thread_wakeup is not None: 

1427 # Wake up queue management thread 

1428 with self._shutdown_lock: 

1429 self._executor_manager_thread_wakeup.wakeup() 

1430 

1431 if executor_manager_thread is not None and wait: 

1432 # This locks avoids concurrent join if the interpreter 

1433 # is shutting down. 

1434 with _global_shutdown_lock: 

1435 executor_manager_thread.join() 

1436 _threads_wakeups.pop(executor_manager_thread, None) 

1437 

1438 # To reduce the risk of opening too many files, remove references to 

1439 # objects that use file descriptors. 

1440 self._executor_manager_thread = None 

1441 self._executor_manager_thread_wakeup = None 

1442 self._call_queue = None 

1443 self._result_queue = None 

1444 self._processes_management_lock = None 

1445 

1446 shutdown.__doc__ = Executor.shutdown.__doc__