diff --git a/billiard/context.py b/billiard/context.py index 5bbc835..427c002 100644 --- a/billiard/context.py +++ b/billiard/context.py @@ -368,7 +368,14 @@ def _check_available(self): 'spawn': SpawnContext(), 'forkserver': ForkServerContext(), } - _default_context = DefaultContext(_concrete_contexts['fork']) + if sys.platform == 'darwin': + # bpo-33725: running arbitrary code after fork() is no longer + # reliable on macOS since macOS 10.14 (Mojave). Use spawn by + # default instead. + # See https://github.com/celery/celery/issues/9894 + _default_context = DefaultContext(_concrete_contexts['spawn']) + else: + _default_context = DefaultContext(_concrete_contexts['fork']) else: diff --git a/t/unit/test_context.py b/t/unit/test_context.py new file mode 100644 index 0000000..d209c50 --- /dev/null +++ b/t/unit/test_context.py @@ -0,0 +1,55 @@ +import subprocess +import sys + + +def _get_default_context_attr(platform, attr): + """Run a subprocess that patches sys.platform before importing billiard. + + This exercises the actual import-time conditional without corrupting + the module state of the current process. + """ + result = subprocess.run( + [sys.executable, '-c', + 'import unittest.mock; ' + f'unittest.mock.patch("sys.platform", {platform!r}).start(); ' + 'import billiard.context; ' + f'print(getattr(billiard.context._default_context, {attr!r})())'], + capture_output=True, text=True, + ) + assert result.returncode == 0, result.stderr + return result.stdout.strip() + + +class test_default_context_darwin: + """Tests that macOS defaults to spawn, matching CPython (bpo-33725).""" + + def test_default_start_method_is_spawn_on_darwin(self): + method = _get_default_context_attr('darwin', 'get_start_method') + assert method == 'spawn' + + def test_default_start_method_is_fork_on_linux(self): + method = _get_default_context_attr('linux', 'get_start_method') + assert method == 'fork' + + def test_set_start_method_override_on_darwin(self): + result = subprocess.run( + [sys.executable, '-c', + 'import unittest.mock; ' + 'unittest.mock.patch("sys.platform", "darwin").start(); ' + 'import billiard.context; ' + 'ctx = billiard.context._default_context; ' + 'assert ctx.get_start_method() == "spawn"; ' + 'ctx.set_start_method("fork", force=True); ' + 'print(ctx.get_start_method())'], + capture_output=True, text=True, + ) + assert result.returncode == 0, result.stderr + assert result.stdout.strip() == 'fork' + + def test_forking_is_enabled_false_on_darwin(self): + enabled = _get_default_context_attr('darwin', 'forking_is_enabled') + assert enabled == 'False' + + def test_forking_is_enabled_true_on_linux(self): + enabled = _get_default_context_attr('linux', 'forking_is_enabled') + assert enabled == 'True'