From d2cc16018baa96bbbed8b57b7c23fae811632746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Tornar=C3=ADa?= Date: Tue, 21 Feb 2023 16:53:04 -0300 Subject: [PATCH 1/2] #26968: add a doctest to catch ecl race in maxima init We run a new instance of sage in a subprocess to ensure maxima is not already initialized. We use a temporary MAXIMA_USERDIR so its empty, and we try to initialize maxima twice in parallel to entice the race. This temporary dir is placed within `DOT_SAGE` so it is easy to try different filesystems. The bug triggers more frequently if `DOT_SAGE` is in a high latency filesystem (e.g. sshfs on a non-local host). The next commit introduces a workaround for the bug. --- src/sage/interfaces/maxima_lib.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index c9ca5e30939..61cac593add 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -77,6 +77,26 @@ sage: bar == foo True +TESTS: + +Check our workaround for a race in ecl works, see :trac:`26968`. +We use a temporary `MAXIMA_USERDIR` so it's empty; we place it +in `DOT_SAGE` since we expect it to have more latency than `/tmp`. + + sage: import tempfile, subprocess + sage: tmpdir = tempfile.TemporaryDirectory(dir=DOT_SAGE) + sage: _ = subprocess.run(['sage', '-c', # long time + ....: f''' + ....: import os + ....: os.environ["MAXIMA_USERDIR"] = "{tmpdir.name}" + ....: if not os.fork(): + ....: import sage.interfaces.maxima_lib + ....: else: + ....: import sage.interfaces.maxima_lib + ....: os.wait() + ....: ''']) + sage: tmpdir.cleanup() + """ # **************************************************************************** From 0b13dfcd50129072aa08d5daba90c96b4caffc09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Tornar=C3=ADa?= Date: Tue, 21 Feb 2023 17:03:08 -0300 Subject: [PATCH 2/2] #26968: workaround for an ecl race in maxima init When maxima is initialized a bug in ecl implementation of `ensure-directories-exist` might result in a runtime error. As a workaround, in case we get a runtime error we use python to create the directory and then continue with maxima initialization. Note that for normal usage the directory will already exist within the user's `DOT_SAGE` so this code will almost never run. However, when running doctests on CI this occasionally triggers. --- src/sage/interfaces/maxima_lib.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index 61cac593add..9459cb70334 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -136,7 +136,23 @@ ecl_eval("(setq $nolabels t))") ecl_eval("(defvar *MAXIMA-LANG-SUBDIR* NIL)") ecl_eval("(set-locale-subdir)") -ecl_eval("(set-pathnames)") + +try: + ecl_eval("(set-pathnames)") +except RuntimeError: + # Recover from :trac:`26968` by creating `*maxima-objdir*` here. + # This cannot be done before calling `(set-pathnames)` since + # `*maxima-objdir*` is computed there. + # We use python `os.makedirs()` which is immune to the race. + # Using `(ensure-directories-exist ...)` in lisp would be + # subject to the same race condition and since `*maxima-objdir*` + # has multiple components this is quite plausible to happen. + maxima_objdir = ecl_eval("*maxima-objdir*").python()[1:-1] + import os + os.makedirs(maxima_objdir, exist_ok=True) + # Call `(set-pathnames)` again to complete its job. + ecl_eval("(set-pathnames)") + ecl_eval("(defun add-lineinfo (x) x)") ecl_eval('(defun principal nil (cond ($noprincipal (diverg)) ((not pcprntd) (merror "Divergent Integral"))))') ecl_eval("(remprop 'mfactorial 'grind)") # don't use ! for factorials (#11539)