[Python-checkins] gh-90473: Decrease recursion limit and skip tests on WASI (GH-92803)

tiran webhook-mailer at python.org
Thu May 19 06:43:56 EDT 2022


https://github.com/python/cpython/commit/137fd3d88aa46669f5717734e823f4c594ab2843
commit: 137fd3d88aa46669f5717734e823f4c594ab2843
branch: main
author: Christian Heimes <christian at python.org>
committer: tiran <christian at python.org>
date: 2022-05-19T12:43:16+02:00
summary:

gh-90473: Decrease recursion limit and skip tests on WASI (GH-92803)

files:
A Misc/NEWS.d/next/Core and Builtins/2022-05-15-15-25-05.gh-issue-90473.MoPHYW.rst
M Include/internal/pycore_ceval.h
M Lib/platform.py
M Lib/test/pythoninfo.py
M Lib/test/support/__init__.py
M Lib/test/test_compile.py
M Lib/test/test_fileio.py
M Lib/test/test_largefile.py
M Lib/test/test_logging.py
M Lib/test/test_os.py
M Lib/test/test_signal.py
M Lib/test/test_tomllib/test_misc.py
M Lib/test/test_zipimport.py
M Modules/timemodule.c
M Parser/parser.c
M Tools/peg_generator/pegen/c_generator.py
M Tools/wasm/README.md
M Tools/wasm/config.site-wasm32-wasi
M configure
M configure.ac

diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index 8dd89c6850794..3efd6beb035a1 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -12,8 +12,14 @@ extern "C" {
 struct pyruntimestate;
 struct _ceval_runtime_state;
 
+/* WASI has limited call stack. wasmtime 0.36 can handle sufficient amount of
+   C stack frames for little more than 750 recursions. */
 #ifndef Py_DEFAULT_RECURSION_LIMIT
-#  define Py_DEFAULT_RECURSION_LIMIT 1000
+#  ifdef __wasi__
+#    define Py_DEFAULT_RECURSION_LIMIT 750
+#  else
+#    define Py_DEFAULT_RECURSION_LIMIT 1000
+#  endif
 #endif
 
 #include "pycore_interp.h"        // PyInterpreterState.eval_frame
diff --git a/Lib/platform.py b/Lib/platform.py
index 3f3f25a2c92d3..c272c407c7776 100755
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -186,6 +186,10 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384):
 
         executable = sys.executable
 
+        if not executable:
+            # sys.executable is not set.
+            return lib, version
+
     V = _comparable_version
     # We use os.path.realpath()
     # here to work around problems with Cygwin not being
diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
index 28549a645b0f5..84e1c047f9219 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -545,8 +545,14 @@ def format_attr(attr, value):
 def collect_socket(info_add):
     import socket
 
-    hostname = socket.gethostname()
-    info_add('socket.hostname', hostname)
+    try:
+        hostname = socket.gethostname()
+    except OSError:
+        # WASI SDK 15.0 does not have gethostname(2).
+        if sys.platform != "wasi":
+            raise
+    else:
+        info_add('socket.hostname', hostname)
 
 
 def collect_sqlite(info_add):
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index e4bda940b3dd6..c284fc67b6402 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -199,6 +199,11 @@ def get_original_stdout():
 def _force_run(path, func, *args):
     try:
         return func(*args)
+    except FileNotFoundError as err:
+        # chmod() won't fix a missing file.
+        if verbose >= 2:
+            print('%s: %s' % (err.__class__.__name__, err))
+        raise
     except OSError as err:
         if verbose >= 2:
             print('%s: %s' % (err.__class__.__name__, err))
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index 487a4fa2a9540..d4357243ddabf 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -109,7 +109,9 @@ def __getitem__(self, key):
         self.assertEqual(d['z'], 12)
 
     def test_extended_arg(self):
-        longexpr = 'x = x or ' + '-x' * 2500
+        # default: 1000 * 2.5 = 2500 repetitions
+        repeat = int(sys.getrecursionlimit() * 2.5)
+        longexpr = 'x = x or ' + '-x' * repeat
         g = {}
         code = '''
 def f(x):
diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py
index e4984d3cd559e..c26cdc028cc89 100644
--- a/Lib/test/test_fileio.py
+++ b/Lib/test/test_fileio.py
@@ -9,7 +9,9 @@
 from weakref import proxy
 from functools import wraps
 
-from test.support import cpython_only, swap_attr, gc_collect, is_emscripten
+from test.support import (
+    cpython_only, swap_attr, gc_collect, is_emscripten, is_wasi
+)
 from test.support.os_helper import (TESTFN, TESTFN_UNICODE, make_bad_fd)
 from test.support.warnings_helper import check_warnings
 from collections import UserList
@@ -65,6 +67,7 @@ def testAttributes(self):
             self.assertRaises((AttributeError, TypeError),
                               setattr, f, attr, 'oops')
 
+    @unittest.skipIf(is_wasi, "WASI does not expose st_blksize.")
     def testBlksize(self):
         # test private _blksize attribute
         blksize = io.DEFAULT_BUFFER_SIZE
diff --git a/Lib/test/test_largefile.py b/Lib/test/test_largefile.py
index 8f6bec1620053..3c11c59baef6e 100644
--- a/Lib/test/test_largefile.py
+++ b/Lib/test/test_largefile.py
@@ -156,6 +156,8 @@ def test_seekable(self):
 def skip_no_disk_space(path, required):
     def decorator(fun):
         def wrapper(*args, **kwargs):
+            if not hasattr(shutil, "disk_usage"):
+                raise unittest.SkipTest("requires shutil.disk_usage")
             if shutil.disk_usage(os.path.realpath(path)).free < required:
                 hsize = int(required / 1024 / 1024)
                 raise unittest.SkipTest(
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index e69afae484aa7..fd562322a7637 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -5280,6 +5280,7 @@ def test_emit_after_closing_in_write_mode(self):
             self.assertEqual(fp.read().strip(), '1')
 
 class RotatingFileHandlerTest(BaseFileTest):
+    @unittest.skipIf(support.is_wasi, "WASI does not have /dev/null.")
     def test_should_not_rollover(self):
         # If maxbytes is zero rollover never occurs
         rh = logging.handlers.RotatingFileHandler(
@@ -5387,6 +5388,7 @@ def rotator(source, dest):
         rh.close()
 
 class TimedRotatingFileHandlerTest(BaseFileTest):
+    @unittest.skipIf(support.is_wasi, "WASI does not have /dev/null.")
     def test_should_not_rollover(self):
         # See bpo-45401. Should only ever rollover regular files
         fh = logging.handlers.TimedRotatingFileHandler(
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 36ad587760d70..ae071821e1ccf 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -11,7 +11,6 @@
 import fractions
 import itertools
 import locale
-import mmap
 import os
 import pickle
 import select
@@ -59,6 +58,10 @@
 except ImportError:
     INT_MAX = PY_SSIZE_T_MAX = sys.maxsize
 
+try:
+    import mmap
+except ImportError:
+    mmap = None
 
 from test.support.script_helper import assert_python_ok
 from test.support import unix_shell
@@ -2167,7 +2170,8 @@ def test_fchown(self):
 
     @unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()')
     @unittest.skipIf(
-        support.is_emscripten, "musl libc issue on Emscripten, bpo-46390"
+        support.is_emscripten or support.is_wasi,
+        "musl libc issue on Emscripten/WASI, bpo-46390"
     )
     def test_fpathconf(self):
         self.check(os.pathconf, "PC_NAME_MAX")
@@ -2460,6 +2464,7 @@ def test_kill_int(self):
         # os.kill on Windows can take an int which gets set as the exit code
         self._kill(100)
 
+    @unittest.skipIf(mmap is None, "requires mmap")
     def _kill_with_event(self, event, name):
         tagname = "test_os_%s" % uuid.uuid1()
         m = mmap.mmap(-1, 1, tagname)
diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
index 6d3b299b24cca..6aa529b062000 100644
--- a/Lib/test/test_signal.py
+++ b/Lib/test/test_signal.py
@@ -107,6 +107,10 @@ def test_interprocess_signal(self):
         script = os.path.join(dirname, 'signalinterproctester.py')
         assert_python_ok(script)
 
+    @unittest.skipUnless(
+        hasattr(signal, "valid_signals"),
+        "requires signal.valid_signals"
+    )
     def test_valid_signals(self):
         s = signal.valid_signals()
         self.assertIsInstance(s, set)
@@ -212,6 +216,7 @@ def test_invalid_fd(self):
         self.assertRaises((ValueError, OSError),
                           signal.set_wakeup_fd, fd)
 
+    @unittest.skipUnless(support.has_socket_support, "needs working sockets.")
     def test_invalid_socket(self):
         sock = socket.socket()
         fd = sock.fileno()
@@ -241,6 +246,7 @@ def test_set_wakeup_fd_result(self):
         self.assertEqual(signal.set_wakeup_fd(-1), -1)
 
     @unittest.skipIf(support.is_emscripten, "Emscripten cannot fstat pipes.")
+    @unittest.skipUnless(support.has_socket_support, "needs working sockets.")
     def test_set_wakeup_fd_socket_result(self):
         sock1 = socket.socket()
         self.addCleanup(sock1.close)
diff --git a/Lib/test/test_tomllib/test_misc.py b/Lib/test/test_tomllib/test_misc.py
index 76fa5905fa49e..378db58f25594 100644
--- a/Lib/test/test_tomllib/test_misc.py
+++ b/Lib/test/test_tomllib/test_misc.py
@@ -6,6 +6,7 @@
 import datetime
 from decimal import Decimal as D
 from pathlib import Path
+import sys
 import tempfile
 import unittest
 
@@ -91,11 +92,13 @@ def test_deepcopy(self):
         self.assertEqual(obj_copy, expected_obj)
 
     def test_inline_array_recursion_limit(self):
-        nest_count = 470
+        # 470 with default recursion limit
+        nest_count = int(sys.getrecursionlimit() * 0.47)
         recursive_array_toml = "arr = " + nest_count * "[" + nest_count * "]"
         tomllib.loads(recursive_array_toml)
 
     def test_inline_table_recursion_limit(self):
-        nest_count = 310
+        # 310 with default recursion limit
+        nest_count = int(sys.getrecursionlimit() * 0.31)
         recursive_table_toml = nest_count * "key = {" + nest_count * "}"
         tomllib.loads(recursive_table_toml)
diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py
index 85dbf4d8f68eb..66789262dd6ca 100644
--- a/Lib/test/test_zipimport.py
+++ b/Lib/test/test_zipimport.py
@@ -804,6 +804,7 @@ def testEmptyFile(self):
         os_helper.create_empty_file(TESTMOD)
         self.assertZipFailure(TESTMOD)
 
+    @unittest.skipIf(support.is_wasi, "mode 000 not supported.")
     def testFileUnreadable(self):
         os_helper.unlink(TESTMOD)
         fd = os.open(TESTMOD, os.O_CREAT, 000)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-15-15-25-05.gh-issue-90473.MoPHYW.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-15-15-25-05.gh-issue-90473.MoPHYW.rst
new file mode 100644
index 0000000000000..1f9f45a511fba
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-15-15-25-05.gh-issue-90473.MoPHYW.rst	
@@ -0,0 +1 @@
+Decrease default recursion limit on WASI to address limited call stack size.
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 7475ef344b72b..18f9ddb909c02 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -1481,7 +1481,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
 
 #elif defined(HAVE_CLOCK_GETTIME) && \
       defined(CLOCK_PROCESS_CPUTIME_ID) && \
-      !defined(__EMSCRIPTEN__)
+      !defined(__EMSCRIPTEN__) && !defined(__wasi__)
 #define HAVE_THREAD_TIME
 
 #if defined(__APPLE__) && defined(__has_attribute) && __has_attribute(availability)
diff --git a/Parser/parser.c b/Parser/parser.c
index adc8d509eb7d7..08bf6d2945600 100644
--- a/Parser/parser.c
+++ b/Parser/parser.c
@@ -7,7 +7,11 @@
 #  define D(x)
 #endif
 
-# define MAXSTACK 6000
+#ifdef __wasi__
+#  define MAXSTACK 4000
+#else
+#  define MAXSTACK 6000
+#endif
 static const int n_keyword_lists = 9;
 static KeywordToken *reserved_keywords[] = {
     (KeywordToken[]) {{NULL, -1}},
diff --git a/Tools/peg_generator/pegen/c_generator.py b/Tools/peg_generator/pegen/c_generator.py
index 56a1e5a5a14fb..65bfd5900a696 100644
--- a/Tools/peg_generator/pegen/c_generator.py
+++ b/Tools/peg_generator/pegen/c_generator.py
@@ -37,7 +37,11 @@
 #  define D(x)
 #endif
 
-# define MAXSTACK 6000
+#ifdef __wasi__
+#  define MAXSTACK 4000
+#else
+#  define MAXSTACK 6000
+#endif
 
 """
 
diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md
index 83806f0581ace..977b2bb2a8ab9 100644
--- a/Tools/wasm/README.md
+++ b/Tools/wasm/README.md
@@ -220,10 +220,27 @@ AddType application/wasm wasm
 
 # WASI (wasm32-wasi)
 
-WASI builds require [WASI SDK](https://github.com/WebAssembly/wasi-sdk) and
-currently [wasix](https://github.com/singlestore-labs/wasix) for POSIX
+WASI builds require [WASI SDK](https://github.com/WebAssembly/wasi-sdk) 15.0+
+and currently [wasix](https://github.com/singlestore-labs/wasix) for POSIX
 compatibility stubs.
 
+## WASI limitations and issues (WASI SDK 15.0)
+
+A lot of Emscripten limitations also apply to WASI. Noticable restrictions
+are:
+
+- Call stack size is limited. Default recursion limit and parser stack size
+  are smaller than in regular Python builds.
+- ``socket(2)`` cannot create new socket file descriptors. WASI programs can
+  call read/write/accept on a file descriptor that is passed into the process.
+- ``socket.gethostname()`` and host name resolution APIs like
+  ``socket.gethostbyname()`` are not implemented and always fail.
+- ``chmod(2)`` is not available. It's not possible to modify file permissions,
+  yet. A future version of WASI may provide a limited ``set_permissions`` API.
+- File locking (``fcntl``) is not available.
+- ``os.pipe()``, ``os.mkfifo()``, and ``os.mknod()`` are not supported.
+
+
 # Detect WebAssembly builds
 
 ## Python code
diff --git a/Tools/wasm/config.site-wasm32-wasi b/Tools/wasm/config.site-wasm32-wasi
index 255e99c279a0a..ee3fc830e3d8a 100644
--- a/Tools/wasm/config.site-wasm32-wasi
+++ b/Tools/wasm/config.site-wasm32-wasi
@@ -17,3 +17,24 @@ ac_cv_header_sys_resource_h=no
 
 # undefined symbols / unsupported features
 ac_cv_func_eventfd=no
+
+# WASI SDK 15.0 has no pipe syscall.
+ac_cv_func_pipe=no
+
+# WASI SDK 15.0 cannot create fifos and special files.
+ac_cv_func_mkfifo=no
+ac_cv_func_mkfifoat=no
+ac_cv_func_mknod=no
+ac_cv_func_mknodat=no
+
+# fdopendir() fails on SDK 15.0,
+# OSError: [Errno 28] Invalid argument: '.'
+ac_cv_func_fdopendir=no
+
+# WASIX stubs we don't want to use.
+ac_cv_func_kill=no
+
+# WASI sockets are limited to operations on given socket fd and inet sockets.
+# Disable AF_UNIX and AF_PACKET support, see socketmodule.h.
+ac_cv_header_sys_un_h=no
+ac_cv_header_netpacket_packet_h=no
diff --git a/configure b/configure
index 02810880e914f..6fa4051310b12 100755
--- a/configure
+++ b/configure
@@ -22611,6 +22611,7 @@ case $ac_sys_system in #(
 
 
     py_cv_module__ctypes_test=n/a
+    py_cv_module_fcntl=n/a
     py_cv_module_=n/a
 
 
diff --git a/configure.ac b/configure.ac
index eab326232b14d..bef4904325b52 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6690,8 +6690,10 @@ AS_CASE([$ac_sys_system],
       ],
       [Emscripten/node*], [],
       [WASI/*], [
+        dnl WASI SDK 15.0 does not support file locking.
         PY_STDLIB_MOD_SET_NA(
           [_ctypes_test],
+	  [fcntl],
         )
       ]
     )



More information about the Python-checkins mailing list