From 444aa4249cb71af4c85cbfc31bed075b64605903 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 25 Jan 2024 23:58:52 -0800 Subject: [PATCH 1/6] src/sage/misc/timing.py: Move import of resource into method --- src/sage/misc/timing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/misc/timing.py b/src/sage/misc/timing.py index 75d73ef2db6..7e13c533e4d 100644 --- a/src/sage/misc/timing.py +++ b/src/sage/misc/timing.py @@ -18,7 +18,6 @@ # **************************************************************************** -import resource import time @@ -77,6 +76,7 @@ def cputime(t=0, subprocesses=False): subprocesses = True if not subprocesses: + import resource try: t = float(t) except TypeError: From 7e178277fc859490a83ffe67a58a10da09845561 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 27 Jan 2024 21:42:25 -0800 Subject: [PATCH 2/6] src/sage/features/threejs.py: Do not error out when threejs-version.txt is not present --- src/sage/features/threejs.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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(): From c04ee156e0e4f692c9ffca92cf2ca94746e5fa07 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 24 May 2024 18:40:42 -0700 Subject: [PATCH 3/6] sage.doctest: multiprocessing import fixes for pyodide --- src/sage/doctest/external.py | 13 +++++++++---- src/sage/doctest/forker.py | 20 ++++++++++++++------ src/sage/parallel/multiprocessing_sage.py | 3 ++- 3 files changed, 25 insertions(+), 11 deletions(-) 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..b9ee31f2c22 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,17 @@ # 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) +try: + import multiprocessing + from multiprocessing import Process +except ImportError: + multiprocessing = None + class Process: + pass + def _sorted_dict_pprinter_factory(start, end): """ @@ -1482,7 +1490,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 +1634,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 +2139,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 +2208,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/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) From e6de27eb418808762ba07f51152000291b8617ab Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 28 Jan 2024 23:02:28 -0800 Subject: [PATCH 4/6] src/sage/doctest/forker.py: Do not fail when sympy but not sympy.printing is available --- src/sage/doctest/forker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index b9ee31f2c22..e0c0745ab2c 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -244,7 +244,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 From ec91df59726336992ec983fb57d40060142b723a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 30 Jan 2024 16:57:50 -0800 Subject: [PATCH 5/6] sage.doctest: multiprocessing import fixes for pyodide (fixup) --- src/sage/doctest/forker.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index e0c0745ab2c..f4b3ac488ab 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -83,13 +83,12 @@ import multiprocessing multiprocessing.set_start_method('fork', force=True) +from multiprocessing import Process + try: - import multiprocessing - from multiprocessing import Process + import _multiprocessing except ImportError: - multiprocessing = None - class Process: - pass + _multiprocessing = None def _sorted_dict_pprinter_factory(start, end): @@ -1490,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 and multiprocessing: + if not self.options.serial and _multiprocessing: os.kill(os.getppid(), signal.SIGINT) raise finally: @@ -1634,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 and multiprocessing: + if not self.options.serial and _multiprocessing: os.kill(os.getppid(), signal.SIGINT) raise finally: @@ -2139,7 +2138,7 @@ def dispatch(self): sage -t .../sage/rings/big_oh.py [... tests, ...s wall] """ - if self.controller.options.serial or not multiprocessing: + if self.controller.options.serial or not _multiprocessing: self.serial_dispatch() else: self.parallel_dispatch() From 224faea914cd4d49c7cd05568d72c8b38bb88815 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 30 Jan 2024 16:58:49 -0800 Subject: [PATCH 6/6] src/sage/misc/timing.py (cputime): If module 'resource' is not available, fall back to walltime --- src/sage/misc/timing.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sage/misc/timing.py b/src/sage/misc/timing.py index 7e13c533e4d..c9e9bf747f9 100644 --- a/src/sage/misc/timing.py +++ b/src/sage/misc/timing.py @@ -72,11 +72,16 @@ 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 if not subprocesses: - import resource try: t = float(t) except TypeError: