Skip to content

Commit d9b945f

Browse files
r-xuescheibelpadamjstewarttgamblin
authored
Mac OS: support Python >= 3.8 by using fork-based multiprocessing (spack#18124)
As detailed in https://bugs.python.org/issue33725, starting new processes with 'fork' on Mac OS is not guaranteed to work in general. As of Python 3.8 the default process spawning mechanism was changed to avoid this issue. Spack depends on the fork-based method to preserve file descriptors transparently, to preserve global state, and to avoid pickling some objects. An effort is underway to remove dependence on fork-based process spawning (see spack#18205). In the meantime, this allows Spack to run with Python 3.8 on Mac OS by explicitly choosing to use 'fork'. Co-authored-by: Peter Josef Scheibel <scheibel1@llnl.gov> Co-authored-by: Adam J. Stewart <ajstewart426@gmail.com> Co-authored-by: Todd Gamblin <tgamblin@llnl.gov>
1 parent 0740a4a commit d9b945f

9 files changed

Lines changed: 35 additions & 14 deletions

File tree

.github/workflows/macos_python.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- uses: actions/checkout@v2
2828
- uses: actions/setup-python@v2
2929
with:
30-
python-version: 3.7
30+
python-version: 3.8
3131
- name: spack install
3232
run: |
3333
. .github/workflows/install_spack.sh
@@ -42,7 +42,7 @@ jobs:
4242
- uses: actions/checkout@v2
4343
- uses: actions/setup-python@v2
4444
with:
45-
python-version: 3.7
45+
python-version: 3.8
4646
- name: spack install
4747
run: |
4848
. .github/workflows/install_spack.sh
@@ -56,7 +56,7 @@ jobs:
5656
- uses: actions/checkout@v2
5757
- uses: actions/setup-python@v2
5858
with:
59-
python-version: 3.7
59+
python-version: 3.8
6060
- name: spack install
6161
run: |
6262
. .github/workflows/install_spack.sh
@@ -71,7 +71,7 @@ jobs:
7171
- uses: actions/checkout@v2
7272
- uses: actions/setup-python@v2
7373
with:
74-
python-version: 3.7
74+
python-version: 3.8
7575
- name: spack install
7676
run: |
7777
. .github/workflows/install_spack.sh

.github/workflows/macos_unit_tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
fetch-depth: 0
1919
- uses: actions/setup-python@v2
2020
with:
21-
python-version: 3.7
21+
python-version: 3.8
2222
- name: Install Python packages
2323
run: |
2424
pip install --upgrade pip six setuptools

.github/workflows/style_and_docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- uses: actions/checkout@v2
1717
- uses: actions/setup-python@v2
1818
with:
19-
python-version: 3.7
19+
python-version: 3.8
2020
- name: Install Python Packages
2121
run: |
2222
pip install --upgrade pip

lib/spack/llnl/util/lang.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from __future__ import division
77

8+
import multiprocessing
89
import os
910
import re
1011
import functools
@@ -19,6 +20,23 @@
1920
ignore_modules = [r'^\.#', '~$']
2021

2122

23+
# On macOS, Python 3.8 multiprocessing now defaults to the 'spawn' start
24+
# method. Spack cannot currently handle this, so force the process to start
25+
# using the 'fork' start method.
26+
#
27+
# TODO: This solution is not ideal, as the 'fork' start method can lead to
28+
# crashes of the subprocess. Figure out how to make 'spawn' work.
29+
#
30+
# See:
31+
# * https://github.com/spack/spack/pull/18124
32+
# * https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods # noqa: E501
33+
# * https://bugs.python.org/issue33725
34+
if sys.version_info >= (3,): # novm
35+
fork_context = multiprocessing.get_context('fork')
36+
else:
37+
fork_context = multiprocessing
38+
39+
2240
def index_by(objects, *funcs):
2341
"""Create a hierarchy of dictionaries by splitting the supplied
2442
set of objects on unique values of the supplied functions.

lib/spack/llnl/util/tty/log.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from six import StringIO
2222

2323
import llnl.util.tty as tty
24+
from llnl.util.lang import fork_context
2425

2526
try:
2627
import termios
@@ -430,7 +431,7 @@ def __enter__(self):
430431
except BaseException:
431432
input_stream = None # just don't forward input if this fails
432433

433-
self.process = multiprocessing.Process(
434+
self.process = fork_context.Process(
434435
target=_writer_daemon,
435436
args=(
436437
input_stream, read_fd, write_fd, self.echo, self.log_file,

lib/spack/llnl/util/tty/pty.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import traceback
2525

2626
import llnl.util.tty.log as log
27+
from llnl.util.lang import fork_context
2728

2829
from spack.util.executable import which
2930

@@ -233,7 +234,7 @@ def start(self, **kwargs):
233234
``minion_function``.
234235
235236
"""
236-
self.proc = multiprocessing.Process(
237+
self.proc = fork_context.Process(
237238
target=PseudoShell._set_up_and_run_controller_function,
238239
args=(self.controller_function, self.minion_function,
239240
self.controller_timeout, self.sleep_time),

lib/spack/spack/build_environment.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
import llnl.util.tty as tty
4646
from llnl.util.tty.color import cescape, colorize
4747
from llnl.util.filesystem import mkdirp, install, install_tree
48-
from llnl.util.lang import dedupe
48+
from llnl.util.lang import dedupe, fork_context
4949

5050
import spack.build_systems.cmake
5151
import spack.build_systems.meson
@@ -886,7 +886,7 @@ def child_process(child_pipe, input_stream):
886886
if sys.stdin.isatty() and hasattr(sys.stdin, 'fileno'):
887887
input_stream = os.fdopen(os.dup(sys.stdin.fileno()))
888888

889-
p = multiprocessing.Process(
889+
p = fork_context.Process(
890890
target=child_process, args=(child_pipe, input_stream))
891891
p.start()
892892

lib/spack/spack/test/database.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"""
1010
import datetime
1111
import functools
12-
import multiprocessing
1312
import os
1413
import pytest
1514
import json
@@ -24,6 +23,7 @@
2423

2524
import llnl.util.lock as lk
2625
from llnl.util.tty.colify import colify
26+
from llnl.util.lang import fork_context
2727

2828
import spack.repo
2929
import spack.store
@@ -524,7 +524,7 @@ def read_and_modify():
524524
with mutable_database.write_transaction():
525525
_mock_remove('mpileaks ^zmpi')
526526

527-
p = multiprocessing.Process(target=read_and_modify, args=())
527+
p = fork_context.Process(target=read_and_modify, args=())
528528
p.start()
529529
p.join()
530530

lib/spack/spack/test/llnl/util/lock.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,14 @@
5151
import glob
5252
import getpass
5353
from contextlib import contextmanager
54-
from multiprocessing import Process, Queue
54+
from multiprocessing import Queue
5555

5656
import pytest
5757

5858
import llnl.util.lock as lk
5959
import llnl.util.multiproc as mp
6060
from llnl.util.filesystem import touch
61+
from llnl.util.lang import fork_context
6162

6263

6364
#
@@ -214,7 +215,7 @@ def local_multiproc_test(*functions, **kwargs):
214215
b = mp.Barrier(len(functions), timeout=barrier_timeout)
215216

216217
args = (b,) + tuple(kwargs.get('extra_args', ()))
217-
procs = [Process(target=f, args=args, name=f.__name__)
218+
procs = [fork_context.Process(target=f, args=args, name=f.__name__)
218219
for f in functions]
219220

220221
for p in procs:

0 commit comments

Comments
 (0)