diff --git a/src/sage/doctest/external.py b/src/sage/doctest/external.py index 56727bd79f6..d99e2e88a77 100644 --- a/src/sage/doctest/external.py +++ b/src/sage/doctest/external.py @@ -31,15 +31,15 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -import multiprocessing import os + # With OS X, Python 3.8 defaults to use 'spawn' instead of 'fork' in # multiprocessing, and Sage doctesting doesn't work with 'spawn'. See # trac #27754. if os.uname().sysname == 'Darwin': + import multiprocessing multiprocessing.set_start_method('fork', force=True) -Array = multiprocessing.Array # Functions in this module whose name is of the form 'has_xxx' tests if the # software xxx is available to Sage. @@ -431,8 +431,13 @@ def __init__(self): features.update(all_features()) self._features = sorted(features, key=lambda feature: feature.name) self._indices = {feature.name: idx for idx, feature in enumerate(self._features)} - self._seen = Array('i', len(self._features)) # initialized to zeroes - self._hidden = Array('i', len(self._features)) # initialized to zeroes + try: + from multiprocessing import Array + self._seen = Array('i', len(self._features)) # initialized to zeroes + self._hidden = Array('i', len(self._features)) # initialized to zeroes + except ImportError: # module '_multiprocessing' is removed in Pyodide due to browser limitations + self._seen = [0] * len(self._features) + self._hidden = [0] * len(self._features) def __contains__(self, item): """ diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index b69c842836c..f4b3ac488ab 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -53,7 +53,6 @@ import signal import linecache import hashlib -import multiprocessing import warnings import re import errno @@ -81,8 +80,16 @@ # multiprocessing, and Sage doctesting doesn't work with 'spawn'. See # trac #27754. if os.uname().sysname == 'Darwin': + import multiprocessing multiprocessing.set_start_method('fork', force=True) +from multiprocessing import Process + +try: + import _multiprocessing +except ImportError: + _multiprocessing = None + def _sorted_dict_pprinter_factory(start, end): """ @@ -236,7 +243,7 @@ def init_sage(controller=None): pass try: - import sympy + import sympy.printing except ImportError: # Do not require sympy for running doctests (Issue #25106). pass @@ -1482,7 +1489,7 @@ def report_failure(self, out, test, example, got, globs): except KeyboardInterrupt: # Assume this is a *real* interrupt. We need to # escalate this to the master doctesting process. - if not self.options.serial: + if not self.options.serial and _multiprocessing: os.kill(os.getppid(), signal.SIGINT) raise finally: @@ -1626,7 +1633,7 @@ def report_unexpected_exception(self, out, test, example, exc_info): except KeyboardInterrupt: # Assume this is a *real* interrupt. We need to # escalate this to the master doctesting process. - if not self.options.serial: + if not self.options.serial and _multiprocessing: os.kill(os.getppid(), signal.SIGINT) raise finally: @@ -2131,13 +2138,13 @@ def dispatch(self): sage -t .../sage/rings/big_oh.py [... tests, ...s wall] """ - if self.controller.options.serial: + if self.controller.options.serial or not _multiprocessing: self.serial_dispatch() else: self.parallel_dispatch() -class DocTestWorker(multiprocessing.Process): +class DocTestWorker(Process): """ The DocTestWorker process runs one :class:`DocTestTask` for a given source. It returns messages about doctest failures (or all tests if @@ -2200,7 +2207,7 @@ def __init__(self, source, options, funclist=[], baseline=None): cumulative wall time: ... seconds Features detected... """ - multiprocessing.Process.__init__(self) + Process.__init__(self) self.source = source self.options = options diff --git a/src/sage/features/threejs.py b/src/sage/features/threejs.py index 8698b84e0e5..33f484ea4e4 100644 --- a/src/sage/features/threejs.py +++ b/src/sage/features/threejs.py @@ -67,8 +67,11 @@ def required_version(self): filename = Path(SAGE_EXTCODE) / 'threejs' / 'threejs-version.txt' - with open(filename) as f: - return f.read().strip() + try: + with open(filename) as f: + return f.read().strip() + except FileNotFoundError: + return "unknown" def all_features(): diff --git a/src/sage/misc/timing.py b/src/sage/misc/timing.py index 75d73ef2db6..c9e9bf747f9 100644 --- a/src/sage/misc/timing.py +++ b/src/sage/misc/timing.py @@ -18,7 +18,6 @@ # **************************************************************************** -import resource import time @@ -73,6 +72,12 @@ def cputime(t=0, subprocesses=False): CPU time is reported correctly because subprocesses can be started and terminated at any given time. """ + try: + import resource + except ImportError: + # The module 'resource' is removed in Pyodide to browser limitations. + return walltime(t) + if isinstance(t, GlobalCputime): subprocesses = True diff --git a/src/sage/parallel/multiprocessing_sage.py b/src/sage/parallel/multiprocessing_sage.py index ab2ee3f95ee..472f1b9194d 100644 --- a/src/sage/parallel/multiprocessing_sage.py +++ b/src/sage/parallel/multiprocessing_sage.py @@ -11,7 +11,6 @@ # https://www.gnu.org/licenses/ ################################################################################ -from multiprocessing import Pool from functools import partial from sage.misc.fpickle import pickle_function, call_pickled_function from . import ncpus @@ -63,6 +62,8 @@ def parallel_iter(processes, f, inputs): sage: v.sort(); v [(((2,), {}), 4), (((3,), {}), 6)] """ + from multiprocessing import Pool + if processes == 0: processes = ncpus.ncpus() p = Pool(processes)