Fix parallelism in cythonize cmd & callable via concurrent.futures#7183
Fix parallelism in cythonize cmd & callable via concurrent.futures#7183webknjaz wants to merge 8 commits intocython:masterfrom
cythonize cmd & callable via concurrent.futures#7183Conversation
This is a workaround for a hang in Cython extention compilation under Python 3.8+ on macOS. It hangs in `multiprocessing` when the `spawn` method is set up so the patch simply sets the workers number to `1` to combat that. Fixes ansible#762 and fixes ansible#769. Upstream fix: cython/cython#7183
This is a workaround for a hang in Cython extention compilation under Python 3.8+ on macOS. It hangs in `multiprocessing` when the `spawn` method is set up so the patch simply sets the workers number to `1` to combat that. Fixes ansible#762 and fixes ansible#769. Upstream fix: cython/cython#7183
This is a workaround for a hang in Cython extention compilation under Python 3.8+ on macOS. It hangs in `multiprocessing` when the `spawn` method is set up so the patch simply sets the workers number to `1` to combat that. Fixes ansible#762 and fixes ansible#769. Upstream fix: cython/cython#7183
This is a workaround for a hang in Cython extention compilation under Python 3.8+ on macOS. It hangs in `multiprocessing` when the `spawn` method is set up so the patch simply sets the workers number to `1` to combat that. Fixes ansible#762 and fixes ansible#769. Upstream fix: cython/cython#7183
CHANGES.rst
Outdated
| is set up to use ``spawn()`` in :mod:`multiprocessing`. Prior to this fix, | ||
| compilation failures would cause ``cythonize`` to hang in parallel mode. | ||
| This would happen under Python 3.8+ on macOS. A related issue in a different |
There was a problem hiding this comment.
I discovered that it hangs on Linux w/ fork() too. The quickest repro is CFLAGS='-boom' pip wheel 'git+https://github.com/ansible/pylibssh.git@a220d06a9c5c39a4609f4a5d574ff92713b2cdf8' -v --no-deps. I don't have a small one atm.
But this also means that #3263 might be incomplete too.
cc @da-woods @scoder where do I go from here? This sounds like a problem described in https://dnmtechs.com/python-multiprocessing-pool-hangs-at-join/.
There was a problem hiding this comment.
I discovered that the setup() call @
cython/Cython/Build/Cythonize.py
Lines 125 to 129 in 26bb419
SystemExit. And that's what makes it hang.
Injecting something like
except BaseException as err:
raise Exceptionin there stops the main process from hanging. However, the cythonize then proceeds and produces a successful return code, which is undesired (forcing parallel=1 makes it fail properly because it bypasses muliprocessing altogeher).
There was a problem hiding this comment.
Looks like raraising as a non-base exception + calling .get() on the result object might be what we want. I'll try playing with our options and report back.
There was a problem hiding this comment.
Untested but: I think concurrent.futures.ProcessPoolExecutor might do a better job of handling unexpected termination. I switched the parallelization in runtests.py to use it because of that
There was a problem hiding this comment.
Okay, I'll add that to the list of things to check. Thanks for the suggestion!
There was a problem hiding this comment.
I tried and confirmed that it doesn't get stuck on Linux. Yet to check macOS.
Cython/Build/Cythonize.py
Outdated
| mp_configured_to_spawn = multiprocessing.get_start_method() == 'spawn' | ||
| if mp_configured_to_spawn: | ||
| print('Disabling parallel cythonization for "spawn" process start method.') | ||
|
|
There was a problem hiding this comment.
I wonder if it's be better just to use an mp_context with a start method that does work rather than disabling it.
There was a problem hiding this comment.
Yeah, I was wondering the same but wanted to focus on making sure the patch works at least somehow and is similar to the other PR before trying to remake everything.
There was a problem hiding this comment.
@da-woods changed to concurrent.futures and checked that the cythonize cmd fails on mac and linux correctly.
3a54d7b to
f283576
Compare
cythonize cmd on spawncythonize cmd & callable via concurrent.futures
f283576 to
22e5333
Compare
22e5333 to
10003f0
Compare
10003f0 to
7c29c6f
Compare
7c29c6f to
a21b057
Compare
Cython/Build/Cythonize.py
Outdated
| try: | ||
| _con_fut.wait(compiler_tasks, return_when=_con_fut.FIRST_EXCEPTION) | ||
| except KeyboardInterrupt: | ||
| for task in compiler_tasks: | ||
| task.cancel() | ||
| raise |
There was a problem hiding this comment.
I wonder if this the try should be broader and wrap the submission step
There was a problem hiding this comment.
I moved this to wrap the entire CM which makes more sense to me.
| except OSError: | ||
| pool = _FakePool() |
There was a problem hiding this comment.
I suspect removing this might break webassembly (for example). We may need to keep the fake pool at least for that case.
There was a problem hiding this comment.
Right. I wasn't sure what could cause that. Plus I don't know if concurrent.futures will raise the same exceptions. Do you have any way of checking this?
There was a problem hiding this comment.
You can quickly check it with https://pyodide.org/en/stable/console.html (just as a basic REPL - I'm not sure how to set up a more complete environment).
It looks like it raises a ModuleNotFoundError rather than an ImportError. So this probably isn't a useful fallback right now anyway.
It also looks like Android doesn't support multiprocessing but I wouldn't know how to test that.
It's probably a good thing to only have one fallback path (whether that's a manual loop or a dummy executor - not sure which). I don't think it's worth trying to explicitly test these more obscure platforms though
There was a problem hiding this comment.
Oh, I see what you mean. And for concurrent.futures, it's NotImplementedError even:
>>> import concurrent.futures
>>> import multiprocessing
>>> pool = multiprocessing.Pool(10)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/lib/python313.zip/multiprocessing/context.py", line 119, in Pool
return Pool(processes, initializer, initargs, maxtasksperchild,
context=self.get_context())
File "/lib/python313.zip/multiprocessing/pool.py", line 191, in __init__
self._setup_queues()
~~~~~~~~~~~~~~~~~~^^
File "/lib/python313.zip/multiprocessing/pool.py", line 346, in _setup_queues
self._inqueue = self._ctx.SimpleQueue()
~~~~~~~~~~~~~~~~~~~~~^^
File "/lib/python313.zip/multiprocessing/context.py", line 113, in SimpleQueue
return SimpleQueue(ctx=self.get_context())
File "/lib/python313.zip/multiprocessing/queues.py", line 361, in __init__
self._rlock = ctx.Lock()
~~~~~~~~^^
File "/lib/python313.zip/multiprocessing/context.py", line 67, in Lock
from .synchronize import Lock
File "/lib/python313.zip/multiprocessing/synchronize.py", line 17, in <module>
import _multiprocessing
ModuleNotFoundError: No module named '_multiprocessing'
>>> concurrent.futures.ProcessPoolExecutor(10)
Traceback (most recent call last):
File "/lib/python313.zip/concurrent/futures/process.py", line 586, in _check_system_limits
import multiprocessing.synchronize
File "/lib/python313.zip/multiprocessing/synchronize.py", line 17, in <module>
import _multiprocessing
ModuleNotFoundError: No module named '_multiprocessing'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/lib/python313.zip/concurrent/futures/process.py", line 651, in __init__
_check_system_limits()
~~~~~~~~~~~~~~~~~~~~^^
File "/lib/python313.zip/concurrent/futures/process.py", line 592, in _check_system_limits
raise NotImplementedError(_system_limited)
NotImplementedError: This Python build lacks multiprocessing.synchronize, usually due to named semaphores being u
navailable on this platform.As for testing, it might be possible to make use of cibuildwheel for smoke tests. But that would be a separate integration effort, I imagine.
@da-woods so should we keep it removed?
There was a problem hiding this comment.
I was wondering if we could catch both OSError and ModuleNotFoundError, but maybe just call _build(ext_modules, parallel=0) as the fallback rather than doing fake executors?
There was a problem hiding this comment.
Alright, I shuffled things around. lmk what you think.
A similar problem was fixed in cython#3263 which is what this patch is porting to another module. That fixed the Python-land API while this change aims to also fix its command-line counterpart. The symptoms are Python 3.8+, macOS and the underlying compiler invocations failing. If these are met, `cythonize` would hang before this commit. Fixes cython#3973. Refs: * cython#3262 * cython#7146 * ansible/pylibssh#769
3a2e39a to
6abe6d4
Compare
|
(rebased w/o changes to see the CI outcome first) |
This is a workaround for a hang in Cython extention compilation under Python 3.8+ on macOS. It hangs in `multiprocessing` when the `spawn` method is set up so the patch simply sets the workers number to `1` to combat that. Fixes ansible#762 and fixes ansible#769. Upstream fix: cython/cython#7183
This is a workaround for a hang in Cython extention compilation under Python 3.8+ on macOS. It hangs in `multiprocessing` when the `spawn` method is set up so the patch simply sets the workers number to `1` to combat that. Fixes ansible#762 and fixes ansible#769. Upstream fix: cython/cython#7183
This is a workaround for a hang in Cython extention compilation under Python 3.8+ on macOS. It hangs in `multiprocessing` when the `spawn` method is set up so the patch simply sets the workers number to `1` to combat that. Fixes ansible#762 and fixes ansible#769. Upstream fix: cython/cython#7183
This adds a new section for `cython` subcommand separate from `cythonize`. It also applies a workaround for the Cython bug that makes it hang in parallel mode on any compiler errors [[1]]. [1]: cython/cython#7183
Co-authored-by: scoder <[email protected]>
Co-authored-by: scoder <[email protected]>
| import concurrent.futures as _con_fut | ||
| import contextlib as _ctx |
There was a problem hiding this comment.
I was trying not to pollute the
from [...] import *namespace with extra public objects.
I understand that. However, this module is not meant for public consumption and it already imports a couple of things globally, so more global names won't hurt. Please use readable names.
| except (OSError, ModuleNotFoundError): | ||
| # `OSError` is a historic exception in `multiprocessing` | ||
| # `ModuleNotFoundError` happens under pyodide |
There was a problem hiding this comment.
Ok, I updated my suggestion.
That said, if "OSError is a historic exception in multiprocessing", then why do we still need it here?
| import cython | ||
|
|
||
| import collections | ||
| import concurrent.futures as _con_fut |
A similar problem was worked around in cython#3263 by disabling parallelism altogether. This patch reverts that and updates the worker management to use concurrent.futures.ProcessPoolExecutor instead of multiprocessing.Pool. The symptoms are Python 3.8+ and the underlying compiler invocations failing. If these are met, cythonize would hang before this commit. Original PR by Sviatoslav Sydorenko in cython#7183 Fixes cython#3973 See cython#3262 See cython#7146
A similar problem was worked around in #3263 by disabling parallelism altogether. This patch reverts that and updates the worker management to use concurrent.futures.ProcessPoolExecutor instead of multiprocessing.Pool. The symptoms are Python 3.8+ and the underlying compiler invocations failing. If these are met, cythonize would hang before this commit. Original PR by Sviatoslav Sydorenko in #7183 Fixes #3973 See #3262 See #7146
|
I cleaned up the imports and pushed your changes as 440b19a, to have them integrated into 3.2b1 in time. Thank you for your contribution. |
A similar problem was worked around in #3263 by disabling parallelism altogether. This patch reverts that and updates the worker management to use concurrent.futures.ProcessPoolExecutor instead of multiprocessing.Pool. The symptoms are Python 3.8+ and the underlying compiler invocations failing. If these are met, cythonize would hang before this commit. Original PR by Sviatoslav Sydorenko in #7183 Fixes #3973 Closes #7183 See #3262 See #7146
|
Thanks! I see you had to re-apply it as 440b19a. P.S. @scoder here's how to preserve commit authorship FYI for the future: https://hynek.me/til/easier-crediting-contributors-github/ |
A similar problem was worked around in #3263 by disabling parallelism altogether. This patch reverts that and updates the worker management to use concurrent.futures.ProcessPoolExecutor instead of multiprocessing.Pool. The symptoms are Python 3.8+ and the underlying compiler invocations failing. If these are met, cythonize would hang before this commit. Original PR by Sviatoslav Sydorenko in #7183 Fixes #3973 Closes #7183 See #3262 See #7146
A similar problem was worked around in #3263 by disabling parallelism altogether. This patch reverts that and updates the worker management to use
concurrent.futures.ProcessPoolExecutorinstead ofmultiprocessing.Pool.The symptoms are Python 3.8+
, macOSand the underlying compiler invocations failing. If these are met,cythonizewould hang before this commit.Fixes #3973.
Refs: