mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Fix fork-child SIGSEGV and yield-from throw() args (#8759)
* Unskip passing flaky CPython tests Remove RUSTPYTHON skip overlays after Linux release repeats passed for enum, threading, logging, subprocess, socket, int, re, strtod, and regrtest cases. Assisted-by: Grok:4.6 * Fix fork-child SIGSEGV and yield-from throw args Forget JoinHandle of threads that did not survive fork instead of dropping them. Zero-reinit the import lock and condvars in the child rather than unlock/notify. Resume the world if os.fork() fails. Call a yield-from subiterator throw() without None fillers so a 1-arg throw stays 1-arg. Drop the stale Windows expectedFailure on logging queue config and unskip test_reinit_tls_after_fork plus the asyncio local_addr family test. Assisted-by: Grok:4.6 * Restore import lock depth after fork; add WASI clock info Zeroing IMP_LOCK in the child discarded its recursion count, so imp.release_lock() raised and test_fork1 exited 1. Re-acquire the saved depth, then drop the extra pre-fork hold. Expose time.get_clock_info on WASI via clock_getres so test_int Stopwatch can run. Unskip lock_tests test_acquire_contended and test_thread_leak. Assisted-by: Grok:4.6 * Honor chmod follow_symlinks=False via fchmodat Linux chmod followed the symlink, so dangling links raised FileNotFoundError instead of NotImplementedError. Use fchmodat with AT_SYMLINK_NOFOLLOW and map ENOTSUP to NotImplementedError. Unskip httpservers truncated Content-Length (CPython already skips as root) and tempfile symlink-mode cleanup on Linux. Assisted-by: Grok:4.6 * Unskip httplib local bad-hostname HTTPS test The skip was leftover. With default ssl-rustls the test passes under -W error on Linux. Assisted-by: Grok:4.6 * Fix list cmp deadlock, wasm digit overflow, REPL prompt Do not hold list/deque locks across item comparison callbacks. Compute int-to-str digit bounds in u64 so the check cannot wrap on wasm32. Snapshot set contents when list.extend copies a set. For a non-TTY stdin, write the REPL prompt to stderr and read lines from stdin. Flush stdout/stderr after each interactive command. Set sys.ps1/ps2 to ">>> " and "... ". Unskip list mutation compares, asyncio all_tasks race, importlib deadlock/parallel/circular-import tests, and REPL stdout flush. Assisted-by: Grok:4.6 * Avoid unnecessary u64 cast in int-to-str digit check bits() is already u64, so `bits as u64` fails clippy unnecessary_cast under -Dwarnings. Assisted-by: Grok:4.6
This commit is contained in:
1 parent
a46627a33c
commit
8fda0eccee
35 files changed
+283
-123
No files matched your search
Vendored
-2
@@ -162,7 +162,6 @@ class BaseLockTests(BaseTestCase):
|
||||
self.assertFalse(result[0])
|
||||
lock.release()
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; sometimes hangs")
|
||||
def test_acquire_contended(self):
|
||||
lock = self.locktype()
|
||||
lock.acquire()
|
||||
@@ -210,7 +209,6 @@ class BaseLockTests(BaseTestCase):
|
||||
with Bunch(f, 1):
|
||||
pass
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; sometimes hangs")
|
||||
def test_thread_leak(self):
|
||||
# The lock shouldn't leak a Thread instance when used from a foreign
|
||||
# (non-threading) thread.
|
||||
|
||||
Vendored
-1
@@ -789,7 +789,6 @@ class EventLoopTestsMixin:
|
||||
self.assertEqual(port, expected)
|
||||
tr.close()
|
||||
|
||||
@unittest.skipIf(sys.platform == "linux", "TODO: RUSTPYTHON; Flaky on CI")
|
||||
@socket_helper.skip_if_tcp_blackhole
|
||||
def test_create_connection_local_addr_skip_different_family(self):
|
||||
# See https://github.com/python/cpython/issues/86508
|
||||
|
||||
@@ -189,10 +189,6 @@ class TestPyFreeThreading(TestFreeThreading, TestCase):
|
||||
def factory(self, loop, coro, **kwargs):
|
||||
return asyncio.tasks._PyTask(coro, loop=loop, **kwargs)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; hangs - Python _current_tasks dict not thread-safe")
|
||||
def test_all_tasks_race(self):
|
||||
return super().test_all_tasks_race()
|
||||
|
||||
|
||||
@unittest.skipUnless(hasattr(asyncio.tasks, "_c_all_tasks"), "requires _asyncio")
|
||||
class TestCFreeThreading(TestFreeThreading, TestCase):
|
||||
@@ -224,10 +220,6 @@ class TestEagerPyFreeThreading(TestPyFreeThreading):
|
||||
def factory(self, loop, coro, eager_start=True, **kwargs):
|
||||
return asyncio.tasks._PyTask(coro, loop=loop, **kwargs, eager_start=eager_start)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; hangs - Python _current_tasks dict not thread-safe")
|
||||
def test_all_tasks_race(self):
|
||||
return super().test_all_tasks_race()
|
||||
|
||||
|
||||
@unittest.skipUnless(hasattr(asyncio.tasks, "_c_all_tasks"), "requires _asyncio")
|
||||
class TestEagerCFreeThreading(TestCFreeThreading, TestCase):
|
||||
|
||||
Vendored
+2
-4
@@ -208,19 +208,17 @@ class CmdLineTest(unittest.TestCase):
|
||||
self.assertIn(b' ~^~', stderr.readline())
|
||||
self.assertIn(b'ZeroDivisionError', stderr.readline())
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; test hang in middle")
|
||||
def test_repl_stdout_flush(self):
|
||||
self.check_repl_stdout_flush()
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; test hang in middle")
|
||||
def test_repl_stdout_flush_separate_stderr(self):
|
||||
self.check_repl_stdout_flush(True)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; test hang in middle")
|
||||
@unittest.skip("TODO: RUSTPYTHON; <stdin> traceback omits the source line and caret")
|
||||
def test_repl_stderr_flush(self):
|
||||
self.check_repl_stderr_flush()
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; test hang in middle")
|
||||
@unittest.skip("TODO: RUSTPYTHON; <stdin> traceback omits the source line and caret")
|
||||
def test_repl_stderr_flush_separate_stderr(self):
|
||||
self.check_repl_stderr_flush(True)
|
||||
|
||||
|
||||
Vendored
-2
@@ -3972,7 +3972,6 @@ class OldTestFlag(unittest.TestCase):
|
||||
self.assertEqual(Color.ALL.value, 7)
|
||||
self.assertEqual(str(Color.BLUE), 'blue')
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
@threading_helper.reap_threads
|
||||
@threading_helper.requires_working_threading()
|
||||
def test_unique_composite(self):
|
||||
@@ -4493,7 +4492,6 @@ class OldTestIntFlag(unittest.TestCase):
|
||||
self.assertEqual(Color.ALL.value, 7)
|
||||
self.assertEqual(str(Color.BLUE), 'blue')
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
@threading_helper.reap_threads
|
||||
@threading_helper.requires_working_threading()
|
||||
def test_unique_composite(self):
|
||||
|
||||
Vendored
-1
@@ -2166,7 +2166,6 @@ class HTTPSTest(TestCase):
|
||||
self.addCleanup(resp.close)
|
||||
self.assertEqual(resp.status, 404)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; Flaky on CI")
|
||||
def test_local_bad_hostname(self):
|
||||
# The (valid) cert doesn't validate the HTTPS hostname
|
||||
import ssl
|
||||
|
||||
Vendored
-1
@@ -1131,7 +1131,6 @@ class CGIHTTPServerTestCase(BaseTestCase):
|
||||
res = self.request('/cgi-bin/file7.py', 'POST', body, headers)
|
||||
self.assertEqual(res.read(), b'%d %d' % (size, size) + self.linesep)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
def test_large_content_length_truncated(self):
|
||||
with support.swap_attr(self.request_handler, 'timeout', 0.001):
|
||||
for w in range(18, 65):
|
||||
|
||||
-2
@@ -99,7 +99,6 @@ class DeadlockAvoidanceTests:
|
||||
self.assertEqual(len(results), NTHREADS)
|
||||
return results
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; sometimes hangs")
|
||||
def test_deadlock(self):
|
||||
results = self.run_deadlock_avoidance_test(True)
|
||||
# At least one of the threads detected a potential deadlock on its
|
||||
@@ -109,7 +108,6 @@ class DeadlockAvoidanceTests:
|
||||
self.assertGreaterEqual(nb_deadlocks, 1)
|
||||
self.assertEqual(results.count((True, True)), len(results) - nb_deadlocks)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
def test_no_deadlock(self):
|
||||
results = self.run_deadlock_avoidance_test(False)
|
||||
self.assertEqual(results.count((True, False)), 0)
|
||||
|
||||
@@ -135,7 +135,6 @@ class ThreadedImportTests(unittest.TestCase):
|
||||
if verbose:
|
||||
print("OK.")
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
@support.bigmemtest(size=50, memuse=76*2**20, dry_run=False)
|
||||
def test_parallel_module_init(self, size):
|
||||
self.check_parallel_module_init()
|
||||
@@ -247,7 +246,6 @@ class ThreadedImportTests(unittest.TestCase):
|
||||
__import__(TESTFN)
|
||||
del sys.modules[TESTFN]
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; hang; Suspected cause of crashes in Windows CI - PermissionError: [WinError 32] Permission denied: \"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\test_python_0cdrhhs_\\test_python_6340æ\"")
|
||||
@support.bigmemtest(size=1, memuse=1.8*2**30, dry_run=False)
|
||||
def test_concurrent_futures_circular_import(self, size):
|
||||
# Regression test for bpo-43515
|
||||
@@ -255,7 +253,6 @@ class ThreadedImportTests(unittest.TestCase):
|
||||
'partial', 'cfimport.py')
|
||||
script_helper.assert_python_ok(fn)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; hang")
|
||||
@support.bigmemtest(size=1, memuse=1.8*2**30, dry_run=False)
|
||||
def test_multiprocessing_pool_circular_import(self, size):
|
||||
# Regression test for bpo-41567
|
||||
|
||||
Vendored
-1
@@ -630,7 +630,6 @@ class IntStrDigitLimitsTests(unittest.TestCase):
|
||||
self.assertIn('conversion', str(err.exception))
|
||||
self.assertLess(sw_fail_extra_huge.seconds, sw_convert.seconds/2)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
def test_denial_of_service_prevented_str_to_int(self):
|
||||
"""Regression test: ensure we fail before performing O(N**2) work."""
|
||||
maxdigits = sys.get_int_max_str_digits()
|
||||
|
||||
Vendored
-2
@@ -226,7 +226,6 @@ class ListTest(list_tests.CommonTest):
|
||||
with self.assertRaises(TypeError):
|
||||
(3,) + L([1,2])
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; hang")
|
||||
def test_equal_operator_modifying_operand(self):
|
||||
# test fix for seg fault reported in bpo-38588 part 2.
|
||||
class X:
|
||||
@@ -252,7 +251,6 @@ class ListTest(list_tests.CommonTest):
|
||||
list4 = [1]
|
||||
self.assertFalse(list3 == list4)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; hang")
|
||||
def test_lt_operator_modifying_operand(self):
|
||||
# See gh-120298
|
||||
class evil:
|
||||
|
||||
Vendored
-3
@@ -731,7 +731,6 @@ class HandlerTest(BaseTest):
|
||||
# based on os.fork existing because that is what users and this test use.
|
||||
# This helps ensure that when fork exists (the important concept) that the
|
||||
# register_at_fork mechanism is also present and used.
|
||||
@unittest.skip("TODO: RUSTPYTHON; Flaky")
|
||||
@support.requires_fork()
|
||||
@threading_helper.requires_working_threading()
|
||||
@skip_if_asan_fork
|
||||
@@ -4109,8 +4108,6 @@ class ConfigDictTest(BaseTest):
|
||||
# log a message (this creates a record put in the queue)
|
||||
logging.getLogger().info(message_to_log)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky EOFError")
|
||||
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; SemLock not implemented on Windows")
|
||||
@skip_if_tsan_fork
|
||||
@support.requires_subprocess()
|
||||
def test_multiprocessing_queues(self):
|
||||
|
||||
Vendored
-1
@@ -2476,7 +2476,6 @@ class ReTests(unittest.TestCase):
|
||||
|
||||
# gh-117594: The test is not slow by itself, but it relies on
|
||||
# the absolute computation time and can fail on very slow computers.
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky, improve perf")
|
||||
@requires_resource('cpu')
|
||||
def test_search_anchor_at_beginning(self):
|
||||
s = 'x'*10**7
|
||||
|
||||
Vendored
-7
@@ -915,7 +915,6 @@ class ProgramsTestCase(BaseTestCase):
|
||||
args = [*self.python_args, script, *self.regrtest_args, *self.tests]
|
||||
self.run_tests(args)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
def test_module_test(self):
|
||||
# -m test
|
||||
args = [*self.python_args, '-m', 'test',
|
||||
@@ -928,14 +927,12 @@ class ProgramsTestCase(BaseTestCase):
|
||||
*self.regrtest_args, *self.tests]
|
||||
self.run_tests(args)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
def test_module_autotest(self):
|
||||
# -m test.autotest
|
||||
args = [*self.python_args, '-m', 'test.autotest',
|
||||
*self.regrtest_args, *self.tests]
|
||||
self.run_tests(args)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
def test_module_from_test_autotest(self):
|
||||
# from test import autotest
|
||||
code = 'from test import autotest'
|
||||
@@ -943,7 +940,6 @@ class ProgramsTestCase(BaseTestCase):
|
||||
*self.regrtest_args, *self.tests]
|
||||
self.run_tests(args)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
def test_script_autotest(self):
|
||||
# Lib/test/autotest.py
|
||||
script = os.path.join(self.testdir, 'autotest.py')
|
||||
@@ -2033,7 +2029,6 @@ class ArgsTestCase(BaseTestCase):
|
||||
for name in names:
|
||||
self.assertFalse(os.path.exists(name), name)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
@unittest.skipIf(support.is_wasi,
|
||||
'checking temp files is not implemented on WASI')
|
||||
def test_leak_tmp_file(self):
|
||||
@@ -2185,11 +2180,9 @@ class ArgsTestCase(BaseTestCase):
|
||||
def test_random_seed(self):
|
||||
self._check_random_seed(run_workers=False)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
def test_random_seed_workers(self):
|
||||
self._check_random_seed(run_workers=True)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
def test_python_command(self):
|
||||
code = textwrap.dedent(r"""
|
||||
import sys
|
||||
|
||||
Vendored
-8
@@ -7066,14 +7066,6 @@ class SendfileUsingSendfileTest(SendfileUsingSendTest):
|
||||
def meth_from_sock(self, sock):
|
||||
return getattr(sock, "_sendfile_use_sendfile")
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; os.sendfile count parameter not handled correctly; flaky")
|
||||
def testCount(self):
|
||||
return super().testCount()
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; os.sendfile count parameter not handled correctly; flaky")
|
||||
def testWithTimeout(self):
|
||||
return super().testWithTimeout()
|
||||
|
||||
|
||||
@unittest.skipUnless(HAVE_SOCKET_ALG, 'AF_ALG required')
|
||||
class LinuxKernelCryptoAPI(unittest.TestCase):
|
||||
|
||||
Vendored
-2
@@ -146,7 +146,6 @@ class StrtodTests(unittest.TestCase):
|
||||
digits *= 5
|
||||
exponent -= 1
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; fails on debug mode, flaky in release mode")
|
||||
def test_halfway_cases(self):
|
||||
# test halfway cases for the round-half-to-even rule
|
||||
for i in range(100 * TEST_SIZE):
|
||||
@@ -213,7 +212,6 @@ class StrtodTests(unittest.TestCase):
|
||||
s = '{}e{}'.format(digits, exponent)
|
||||
self.check_strtod(s)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
def test_parsing(self):
|
||||
# make '0' more likely to be chosen than other digits
|
||||
digits = '000000123456789'
|
||||
|
||||
Vendored
-2
@@ -3051,7 +3051,6 @@ class POSIXProcessTestCase(BaseTestCase):
|
||||
p1.stdout.close()
|
||||
p2.stdout.close()
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
def test_close_fds(self):
|
||||
fd_status = support.findfile("fd_status.py", subdir="subprocessdata")
|
||||
|
||||
@@ -3183,7 +3182,6 @@ class POSIXProcessTestCase(BaseTestCase):
|
||||
# descriptor of a pipe closed in the parent process is valid in the
|
||||
# child process according to fstat(), but the mode of the file
|
||||
# descriptor is invalid, and read or write raise an error.
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
@support.requires_mac_ver(10, 5)
|
||||
def test_pass_fds(self):
|
||||
fd_status = support.findfile("fd_status.py", subdir="subprocessdata")
|
||||
|
||||
Vendored
-1
@@ -1751,7 +1751,6 @@ class TestTemporaryDirectory(BaseTestCase):
|
||||
d2.cleanup()
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; flaky, sometimes pollute env on CI")
|
||||
@unittest.expectedFailureIf(sys.platform in ('android', 'linux'), "TODO: RUSTPYTHON; FileNotFoundError: [Errno 2] No such file or directory: '<LONG_PATH>'")
|
||||
@os_helper.skip_unless_symlink
|
||||
def test_cleanup_with_symlink_modes(self):
|
||||
# cleanup() should not follow symlinks when fixing mode bits (#91133)
|
||||
|
||||
Vendored
-14
@@ -685,7 +685,6 @@ class ThreadTests(BaseTestCase):
|
||||
self.assertEqual(out, b'')
|
||||
self.assertEqual(err, b'')
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
@skip_unless_reliable_fork
|
||||
def test_is_alive_after_fork(self):
|
||||
# Try hard to trigger #18418: is_alive() could sometimes be True on
|
||||
@@ -749,7 +748,6 @@ class ThreadTests(BaseTestCase):
|
||||
"main ident True\n"
|
||||
"current is main True\n")
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky; process timeout after fork")
|
||||
@skip_unless_reliable_fork
|
||||
@unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
|
||||
def test_main_thread_after_fork_from_nonmain_thread(self):
|
||||
@@ -1470,7 +1468,6 @@ class ThreadJoinOnShutdown(BaseTestCase):
|
||||
"""
|
||||
self._run_and_join(script)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
@skip_unless_reliable_fork
|
||||
def test_3_join_in_forked_from_thread(self):
|
||||
# Like the test above, but fork() was called from a worker thread
|
||||
@@ -1571,7 +1568,6 @@ class ThreadJoinOnShutdown(BaseTestCase):
|
||||
self.assertEqual(out.strip(), b"OK")
|
||||
self.assertEqual(rc, 0)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; - flaky, parking_lot mutex not fork-safe")
|
||||
@skip_unless_reliable_fork
|
||||
def test_reinit_tls_after_fork(self):
|
||||
# Issue #13817: fork() would deadlock in a multithreaded program with
|
||||
@@ -1852,7 +1848,6 @@ class ThreadingExceptionTests(BaseTestCase):
|
||||
lock = threading.Lock()
|
||||
self.assertRaises(RuntimeError, lock.release)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
@requires_subprocess()
|
||||
def test_recursion_limit(self):
|
||||
# Issue 9670
|
||||
@@ -2236,10 +2231,6 @@ class CRLockTests(lock_tests.RLockTests):
|
||||
CustomRLock(1, b=2)
|
||||
self.assertEqual(warnings_log, [])
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
def test_different_thread(self):
|
||||
return super().test_different_thread()
|
||||
|
||||
class EventTests(lock_tests.EventTests):
|
||||
eventtype = staticmethod(threading.Event)
|
||||
|
||||
@@ -2250,10 +2241,6 @@ class ConditionAsRLockTests(lock_tests.RLockTests):
|
||||
def test_recursion_count(self):
|
||||
self.skipTest("Condition does not expose _recursion_count()")
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
def test_different_thread(self):
|
||||
return super().test_different_thread()
|
||||
|
||||
class ConditionTests(lock_tests.ConditionTests):
|
||||
condtype = staticmethod(threading.Condition)
|
||||
|
||||
@@ -2413,7 +2400,6 @@ class InterruptMainTests(unittest.TestCase):
|
||||
# Restore original handler
|
||||
signal.signal(signum, handler)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky")
|
||||
@requires_gil_enabled("gh-118433: Flaky due to a longstanding bug")
|
||||
def test_interrupt_main_subthread(self):
|
||||
# Calling start_new_thread with a function that executes interrupt_main
|
||||
|
||||
Vendored
-1
@@ -26,7 +26,6 @@ def target(local, weaklist):
|
||||
|
||||
class BaseLocalTest:
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; flaky test")
|
||||
def test_local_refs(self):
|
||||
self._local_refs(20)
|
||||
self._local_refs(50)
|
||||
|
||||
@@ -177,6 +177,16 @@ pub fn chroot(path: &Path) -> std::io::Result<()> {
|
||||
nix::unistd::chroot(path).map_err(std::io::Error::from)
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
pub fn fchmodat(dirfd: i32, path: &CStr, mode: libc::mode_t, flags: i32) -> std::io::Result<()> {
|
||||
let ret = unsafe { libc::fchmodat(dirfd, path.as_ptr(), mode, flags) };
|
||||
if ret == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd"))]
|
||||
pub fn lchmod(path: &CStr, mode: libc::mode_t) -> std::io::Result<()> {
|
||||
unsafe extern "C" {
|
||||
|
||||
@@ -18,6 +18,22 @@ pub enum ReadlineResult {
|
||||
Other(OtherError),
|
||||
}
|
||||
|
||||
fn stdio_readline(prompt: &str) -> ReadlineResult {
|
||||
use std::io::prelude::*;
|
||||
eprint!("{prompt}");
|
||||
if let Err(e) = io::stderr().flush() {
|
||||
return ReadlineResult::Io(e);
|
||||
}
|
||||
|
||||
let line = io::stdin().lock().lines().next();
|
||||
match line {
|
||||
Some(Ok(line)) => ReadlineResult::Line(line),
|
||||
None => ReadlineResult::Eof,
|
||||
Some(Err(e)) if e.kind() == io::ErrorKind::Interrupted => ReadlineResult::Interrupt,
|
||||
Some(Err(e)) => ReadlineResult::Io(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub mod basic_readline {
|
||||
use super::*;
|
||||
@@ -48,19 +64,7 @@ pub mod basic_readline {
|
||||
}
|
||||
|
||||
pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
|
||||
use std::io::prelude::*;
|
||||
print!("{prompt}");
|
||||
if let Err(e) = io::stdout().flush() {
|
||||
return ReadlineResult::Io(e);
|
||||
}
|
||||
|
||||
let next_line = io::stdin().lock().lines().next();
|
||||
match next_line {
|
||||
Some(Ok(line)) => ReadlineResult::Line(line),
|
||||
None => ReadlineResult::Eof,
|
||||
Some(Err(e)) if e.kind() == io::ErrorKind::Interrupted => ReadlineResult::Interrupt,
|
||||
Some(Err(e)) => ReadlineResult::Io(e),
|
||||
}
|
||||
stdio_readline(prompt)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +130,13 @@ pub mod rustyline_readline {
|
||||
}
|
||||
|
||||
pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
|
||||
use std::io::IsTerminal;
|
||||
// Pipes have no termios. Write the prompt to stderr and read
|
||||
// stdin, matching PyOS_StdioReadline, so `python -i` under
|
||||
// spawn still emits `>>> `.
|
||||
if !io::stdin().is_terminal() {
|
||||
return stdio_readline(prompt);
|
||||
}
|
||||
use rustyline::error::ReadlineError;
|
||||
loop {
|
||||
break match self.repl.readline(prompt) {
|
||||
|
||||
@@ -302,6 +302,17 @@ pub fn clock_getres(id: ClockId) -> std::io::Result<Duration> {
|
||||
.map_err(std::io::Error::from)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
pub fn clock_getres(id: ClockId) -> std::io::Result<Duration> {
|
||||
let mut ts = core::mem::MaybeUninit::<libc::timespec>::uninit();
|
||||
let ret = unsafe { libc::clock_getres(id.as_raw(), ts.as_mut_ptr()) };
|
||||
if ret != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
let ts = unsafe { ts.assume_init() };
|
||||
Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32))
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "redox"), not(target_vendor = "apple")))]
|
||||
pub fn clock_settime(id: ClockId, time: Duration) -> std::io::Result<()> {
|
||||
let ts = nix::sys::time::TimeSpec::from(time);
|
||||
|
||||
@@ -707,8 +707,11 @@ pub(crate) fn check_int_to_str_digits(value: &BigInt, vm: &VirtualMachine) -> Py
|
||||
return Ok(());
|
||||
}
|
||||
// Upper bound on decimal digit count: ⌈bits × log10(2)⌉ + 1, with log10(2) ≈ 0.30103.
|
||||
let digits_upper = (bits as usize * 30103 / 100000) + 1;
|
||||
if digits_upper > limit {
|
||||
// Multiply with the u64 bit count: `bits as usize * 30103` wraps on
|
||||
// 32-bit (wasm32) once bits ≳ 142_500, which silently accepts over-limit
|
||||
// conversions.
|
||||
let digits_upper = bits.saturating_mul(30103) / 100_000 + 1;
|
||||
if digits_upper > u64::try_from(limit).unwrap_or(u64::MAX) {
|
||||
return Err(vm.new_value_error(format!(
|
||||
"Exceeds the limit ({limit} digits) for integer string conversion; \
|
||||
use sys.set_int_max_str_digits() to increase the limit"
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::{
|
||||
class::PyClassImpl,
|
||||
convert::ToPyObject,
|
||||
function::{ArgSize, Either, FuncArgs, OptionalArg, PyComparisonValue},
|
||||
iter::PyExactSizeIterator,
|
||||
protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods},
|
||||
recursion::ReprGuard,
|
||||
sequence::{MutObjectSequenceOp, OptionalRangeArgs, SequenceExt, SequenceMutExt},
|
||||
@@ -590,11 +589,21 @@ impl Comparable for PyList {
|
||||
return Ok(res.into());
|
||||
}
|
||||
let other = class_or_notimplemented!(Self, other);
|
||||
let a = &*zelf.borrow_vec();
|
||||
let b = &*other.borrow_vec();
|
||||
a.iter()
|
||||
.richcompare(b.iter(), op, vm)
|
||||
.map(PyComparisonValue::Implemented)
|
||||
// Item comparison can mutate either list (clear, resize). Holding the
|
||||
// element lock across that callback deadlocks on the write side.
|
||||
crate::iter::richcompare_mutating_seqs(
|
||||
|i| {
|
||||
let a = zelf.borrow_vec();
|
||||
(a.len(), a.get(i).cloned())
|
||||
},
|
||||
|i| {
|
||||
let b = other.borrow_vec();
|
||||
(b.len(), b.get(i).cloned())
|
||||
},
|
||||
op,
|
||||
vm,
|
||||
)
|
||||
.map(PyComparisonValue::Implemented)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -4033,7 +4033,19 @@ impl ExecutingFrame<'_> {
|
||||
Either::A(coro) => coro
|
||||
.throw(jen, exc_type, exc_val, exc_tb, vm)
|
||||
.to_pyresult(vm),
|
||||
Either::B(meth) => meth.call((exc_type, exc_val, exc_tb), vm),
|
||||
Either::B(meth) => {
|
||||
// Omit trailing None so a 1-arg throw() stays 1-arg.
|
||||
// Passing None fillers makes throw() look like the
|
||||
// deprecated 3-arg form and warns under -W error.
|
||||
let args = if !vm.is_none(&exc_tb) {
|
||||
vec![exc_type, exc_val, exc_tb]
|
||||
} else if !vm.is_none(&exc_val) {
|
||||
vec![exc_type, exc_val]
|
||||
} else {
|
||||
vec![exc_type]
|
||||
};
|
||||
meth.call(args, vm)
|
||||
}
|
||||
};
|
||||
return ret.map(ExecutionResult::Yield).or_else(|err| {
|
||||
// Add traceback entry for the yield-from/await point.
|
||||
|
||||
@@ -51,3 +51,51 @@ pub trait PyExactSizeIterator<'a>: ExactSizeIterator<Item = &'a PyObjectRef> + S
|
||||
}
|
||||
|
||||
impl<'a, T> PyExactSizeIterator<'a> for T where T: ExactSizeIterator<Item = &'a PyObjectRef> + Sized {}
|
||||
|
||||
/// Compare two sequences whose item-comparison callbacks may mutate the
|
||||
/// containers. `get(i)` must snapshot `(len, item_at_i)` and drop any
|
||||
/// container lock before returning.
|
||||
///
|
||||
/// After an unequal item, sizes are read again: if a callback shrank a
|
||||
/// sequence so `i` is now past the end, the result is a length comparison
|
||||
/// rather than the item inequality.
|
||||
pub fn richcompare_mutating_seqs(
|
||||
mut get_a: impl FnMut(usize) -> (usize, Option<PyObjectRef>),
|
||||
mut get_b: impl FnMut(usize) -> (usize, Option<PyObjectRef>),
|
||||
op: PyComparisonOp,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<bool> {
|
||||
if matches!(op, PyComparisonOp::Eq | PyComparisonOp::Ne) {
|
||||
let (a_len, _) = get_a(0);
|
||||
let (b_len, _) = get_b(0);
|
||||
if a_len != b_len {
|
||||
return Ok(op == PyComparisonOp::Ne);
|
||||
}
|
||||
}
|
||||
|
||||
let mut i = 0usize;
|
||||
loop {
|
||||
let (a_len, a_item) = get_a(i);
|
||||
let (b_len, b_item) = get_b(i);
|
||||
let (Some(a_item), Some(b_item)) = (a_item, b_item) else {
|
||||
return Ok(op.eval_ord(a_len.cmp(&b_len)));
|
||||
};
|
||||
|
||||
if vm.bool_eq(&a_item, &b_item)? {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let (a_len, _) = get_a(i);
|
||||
let (b_len, _) = get_b(i);
|
||||
if i >= a_len || i >= b_len {
|
||||
return Ok(op.eval_ord(a_len.cmp(&b_len)));
|
||||
}
|
||||
|
||||
return match op {
|
||||
PyComparisonOp::Eq => Ok(false),
|
||||
PyComparisonOp::Ne => Ok(true),
|
||||
_ => a_item.rich_compare_bool(&b_item, op, vm),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ mod _collections {
|
||||
common::lock::{PyMutex, PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard},
|
||||
convert::ToPyObject,
|
||||
function::{FuncArgs, KwArgs, OptionalArg, PyComparisonValue},
|
||||
iter::PyExactSizeIterator,
|
||||
object::{Traverse, TraverseFn},
|
||||
protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
|
||||
recursion::ReprGuard,
|
||||
@@ -587,11 +586,19 @@ mod _collections {
|
||||
}
|
||||
|
||||
let other = class_or_notimplemented!(Self, other);
|
||||
let lhs = zelf.borrow_deque();
|
||||
let rhs = other.borrow_deque();
|
||||
lhs.iter()
|
||||
.richcompare(rhs.iter(), op, vm)
|
||||
.map(PyComparisonValue::Implemented)
|
||||
crate::iter::richcompare_mutating_seqs(
|
||||
|i| {
|
||||
let lhs = zelf.borrow_deque();
|
||||
(lhs.len(), lhs.get(i).cloned())
|
||||
},
|
||||
|i| {
|
||||
let rhs = other.borrow_deque();
|
||||
(rhs.len(), rhs.get(i).cloned())
|
||||
},
|
||||
op,
|
||||
vm,
|
||||
)
|
||||
.map(PyComparisonValue::Implemented)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,22 @@ pub(super) use crate::vm::resolve_frozen_alias;
|
||||
#[pymodule(sub)]
|
||||
mod lock {
|
||||
use crate::{PyResult, VirtualMachine, stdlib::_thread::RawRMutex};
|
||||
use core::cell::Cell;
|
||||
|
||||
static IMP_LOCK: RawRMutex = RawRMutex::INIT;
|
||||
|
||||
thread_local! {
|
||||
static IMP_LOCK_DEPTH: Cell<usize> = const { Cell::new(0) };
|
||||
}
|
||||
|
||||
fn bump_depth() {
|
||||
IMP_LOCK_DEPTH.with(|c| c.set(c.get() + 1));
|
||||
}
|
||||
|
||||
fn drop_depth() {
|
||||
IMP_LOCK_DEPTH.with(|c| c.set(c.get().saturating_sub(1)));
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn acquire_lock(vm: &VirtualMachine) {
|
||||
// Detach while blocking on IMP_LOCK. The import lock is held across
|
||||
@@ -27,10 +40,11 @@ mod lock {
|
||||
|
||||
#[pyfunction]
|
||||
fn release_lock(vm: &VirtualMachine) -> PyResult<()> {
|
||||
if !IMP_LOCK.is_locked() {
|
||||
if !IMP_LOCK.is_locked() || !IMP_LOCK.is_owned_by_current_thread() {
|
||||
Err(vm.new_runtime_error("Global import lock not held"))
|
||||
} else {
|
||||
unsafe { IMP_LOCK.unlock() };
|
||||
drop_depth();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -42,41 +56,44 @@ mod lock {
|
||||
|
||||
pub(super) fn acquire_lock_for_fork() {
|
||||
IMP_LOCK.lock();
|
||||
bump_depth();
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "host_env"))]
|
||||
pub(super) fn release_lock_after_fork_parent() {
|
||||
if IMP_LOCK.is_locked() && IMP_LOCK.is_owned_by_current_thread() {
|
||||
unsafe { IMP_LOCK.unlock() };
|
||||
drop_depth();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset import lock after fork() — only if held by a dead thread.
|
||||
/// Reset the import lock after `fork()`.
|
||||
///
|
||||
/// `IMP_LOCK` is a reentrant mutex. If the *current* (surviving) thread
|
||||
/// held it at fork time, the child must be able to release it normally.
|
||||
/// Only reset if a now-dead thread was the owner.
|
||||
/// Zero the lock so waiter queues from dead threads are discarded, then
|
||||
/// re-acquire it as many times as this thread held it. `unlock()` on the
|
||||
/// inherited lock would unpark those waiters.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Must only be called from single-threaded child after fork().
|
||||
#[cfg(all(unix, feature = "host_env"))]
|
||||
pub(crate) unsafe fn reinit_after_fork() {
|
||||
if IMP_LOCK.is_locked() && !IMP_LOCK.is_owned_by_current_thread() {
|
||||
// Held by a dead thread — reset to unlocked.
|
||||
unsafe { rustpython_common::lock::zero_reinit_after_fork(&IMP_LOCK) };
|
||||
let depth = IMP_LOCK_DEPTH.with(Cell::get);
|
||||
unsafe { rustpython_common::lock::zero_reinit_after_fork(&IMP_LOCK) };
|
||||
for _ in 0..depth {
|
||||
IMP_LOCK.lock();
|
||||
}
|
||||
}
|
||||
|
||||
/// Match CPython's `_PyImport_ReInitLock()` + `_PyImport_ReleaseLock()`
|
||||
/// behavior in the post-fork child:
|
||||
/// 1) if ownership metadata is stale (dead owner / changed tid), reset;
|
||||
/// 2) if current thread owns the lock, release it.
|
||||
/// Restore the child's import lock, then drop the extra hold taken
|
||||
/// before `fork()`. Nested `imp.acquire_lock()` holds stay so the
|
||||
/// child's `release_lock()` can unwind them.
|
||||
#[cfg(all(unix, feature = "host_env"))]
|
||||
pub(super) unsafe fn after_fork_child_reinit_and_release() {
|
||||
unsafe { reinit_after_fork() };
|
||||
if IMP_LOCK.is_locked() && IMP_LOCK.is_owned_by_current_thread() {
|
||||
unsafe { IMP_LOCK.unlock() };
|
||||
drop_depth();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1399,14 +1399,20 @@ pub(crate) mod _thread {
|
||||
}
|
||||
|
||||
inner_guard.state = ThreadHandleState::Done;
|
||||
inner_guard.join_handle = None;
|
||||
// The OS thread did not survive the fork. Dropping JoinHandle
|
||||
// would pthread_detach a copied thread descriptor.
|
||||
if let Some(handle) = inner_guard.join_handle.take() {
|
||||
core::mem::forget(handle);
|
||||
}
|
||||
drop(inner_guard);
|
||||
|
||||
// Reinit and set the done event
|
||||
// Reinit and set the done event. Do not notify: no other
|
||||
// thread exists in the child, and notify_all would unpark
|
||||
// waiters that did not survive the fork.
|
||||
let (lock, cvar) = &*done_event;
|
||||
reinit_parking_lot_mutex(lock);
|
||||
reinit_parking_lot_condvar(cvar);
|
||||
*lock.lock() = true;
|
||||
cvar.notify_all();
|
||||
|
||||
true
|
||||
});
|
||||
@@ -1438,8 +1444,8 @@ pub(crate) mod _thread {
|
||||
|
||||
let (lock, cvar) = &*done_event;
|
||||
reinit_parking_lot_mutex(lock);
|
||||
reinit_parking_lot_condvar(cvar);
|
||||
*lock.lock() = true;
|
||||
cvar.notify_all();
|
||||
|
||||
false
|
||||
});
|
||||
@@ -1465,6 +1471,14 @@ pub(crate) mod _thread {
|
||||
unsafe { rustpython_common::lock::zero_reinit_after_fork(mutex.raw()) };
|
||||
}
|
||||
|
||||
/// Reset a parking_lot::Condvar after fork.
|
||||
///
|
||||
/// `notify_all()` would unpark waiters that did not survive the fork.
|
||||
#[cfg(all(unix, feature = "host_env"))]
|
||||
fn reinit_parking_lot_condvar(cvar: &parking_lot::Condvar) {
|
||||
unsafe { rustpython_common::lock::zero_reinit_after_fork(cvar) };
|
||||
}
|
||||
|
||||
// Thread handle state enum
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ThreadHandleState {
|
||||
|
||||
@@ -614,8 +614,8 @@ pub mod module {
|
||||
#[cfg(feature = "threading")]
|
||||
crate::stdlib::_thread::after_fork_child(vm);
|
||||
|
||||
// CPython parity: reinit import lock ownership metadata in child
|
||||
// and release the lock acquired by PyOS_BeforeFork().
|
||||
// Reinit import lock ownership metadata in child and release the lock
|
||||
// acquired before fork.
|
||||
#[cfg(feature = "threading")]
|
||||
unsafe {
|
||||
crate::stdlib::_imp::after_fork_child_imp_lock_release()
|
||||
@@ -817,15 +817,18 @@ pub mod module {
|
||||
Ok(0)
|
||||
}
|
||||
Ok(pid) => {
|
||||
// Match CPython timing: capture this before parent after-fork hooks
|
||||
// in case those hooks start threads.
|
||||
// Capture this before parent after-fork hooks in case those
|
||||
// hooks start threads.
|
||||
let num_os_threads = get_number_of_os_threads();
|
||||
py_os_after_fork_parent(vm);
|
||||
// Match CPython timing: warn only after parent callback path resumes world.
|
||||
// Warn only after parent callback path resumes the world.
|
||||
warn_if_multi_threaded("fork", num_os_threads, vm);
|
||||
Ok(pid)
|
||||
}
|
||||
Err(err) => Err(err.into_pyexception(vm)),
|
||||
Err(err) => {
|
||||
py_os_after_fork_parent(vm);
|
||||
Err(err.into_pyexception(vm))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,6 +1005,32 @@ pub mod module {
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let [] = dir_fd.0;
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "redox"),
|
||||
not(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd"))
|
||||
))]
|
||||
if !follow_symlinks.0 {
|
||||
let err_path = path.clone();
|
||||
let c_path = path.into_cstring(vm)?;
|
||||
return rustpython_host_env::posix::fchmodat(
|
||||
libc::AT_FDCWD,
|
||||
&c_path,
|
||||
mode as libc::mode_t,
|
||||
libc::AT_SYMLINK_NOFOLLOW,
|
||||
)
|
||||
.map_err(|err| {
|
||||
let enotsup = err.raw_os_error() == Some(libc::EOPNOTSUPP)
|
||||
|| err.raw_os_error() == Some(libc::ENOTSUP);
|
||||
if enotsup {
|
||||
vm.new_not_implemented_error(
|
||||
"chmod: follow_symlinks unavailable on this platform".to_owned(),
|
||||
)
|
||||
} else {
|
||||
OSErrorBuilder::with_filename(&err, err_path, vm)
|
||||
}
|
||||
});
|
||||
}
|
||||
let err_path = path.clone();
|
||||
let body = move || {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
@@ -248,9 +248,9 @@ pub mod sys {
|
||||
};
|
||||
|
||||
#[pyattr(name = "ps1")]
|
||||
const PS1: &str = ">>>>> ";
|
||||
const PS1: &str = ">>> ";
|
||||
#[pyattr(name = "ps2")]
|
||||
const PS2: &str = "..... ";
|
||||
const PS2: &str = "... ";
|
||||
|
||||
#[cfg(windows)]
|
||||
#[pyattr(name = "_vpath")]
|
||||
|
||||
@@ -21,6 +21,11 @@ mod decl {
|
||||
function::{Either, FuncArgs, OptionalArg},
|
||||
types::{PyStructSequence, PyStructSequenceData, struct_sequence_new},
|
||||
};
|
||||
#[cfg(target_os = "wasi")]
|
||||
use crate::{
|
||||
PyRef,
|
||||
builtins::{PyNamespace, PyUtf8StrRef},
|
||||
};
|
||||
#[cfg(any(unix, windows))]
|
||||
use crate::{
|
||||
common::wtf8::Wtf8Buf,
|
||||
@@ -75,6 +80,40 @@ mod decl {
|
||||
get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
fn clock_getres(id: ClockId, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
host_time::clock_getres(id)
|
||||
.map(|d| d.as_secs_f64())
|
||||
.map_err(|err| vm.new_os_error(err.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
#[pyfunction]
|
||||
fn get_clock_info(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
|
||||
let (adj, imp, mono, res) = match name.as_str() {
|
||||
"monotonic" | "perf_counter" => (
|
||||
false,
|
||||
"time.clock_gettime(CLOCK_MONOTONIC)",
|
||||
true,
|
||||
clock_getres(ClockId::CLOCK_MONOTONIC, vm)?,
|
||||
),
|
||||
"time" => (
|
||||
true,
|
||||
"time.clock_gettime(CLOCK_REALTIME)",
|
||||
false,
|
||||
clock_getres(ClockId::CLOCK_REALTIME, vm)?,
|
||||
),
|
||||
_ => return Err(vm.new_value_error("unknown clock")),
|
||||
};
|
||||
|
||||
Ok(py_namespace!(vm, {
|
||||
"implementation" => vm.new_pyobj(imp),
|
||||
"monotonic" => vm.ctx.new_bool(mono),
|
||||
"adjustable" => vm.ctx.new_bool(adj),
|
||||
"resolution" => vm.ctx.new_float(res),
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix, windows, target_os = "wasi")))]
|
||||
fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
|
||||
duration_since_system_now(vm)
|
||||
|
||||
@@ -24,8 +24,9 @@ mod vm_ops;
|
||||
use crate::{
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
|
||||
builtins::{
|
||||
self, PyBaseExceptionRef, PyBaseObject, PyDict, PyDictRef, PyInt, PyList, PyModule, PyStr,
|
||||
PyStrInterned, PyStrRef, PyTypeRef, PyUtf8Str, PyUtf8StrInterned, PyWeak,
|
||||
self, PyBaseExceptionRef, PyBaseObject, PyDict, PyDictRef, PyFrozenSet, PyInt, PyList,
|
||||
PyModule, PySet, PyStr, PyStrInterned, PyStrRef, PyTypeRef, PyUtf8Str, PyUtf8StrInterned,
|
||||
PyWeak,
|
||||
code::PyCode,
|
||||
dict::{PyDictItems, PyDictKeys, PyDictValues},
|
||||
pystr::AsPyStr,
|
||||
@@ -3288,6 +3289,12 @@ impl VirtualMachine {
|
||||
i += 1;
|
||||
}
|
||||
return Ok(results);
|
||||
} else if cls.is(self.ctx.types.set_type) {
|
||||
let keys = value.downcast_ref::<PySet>().unwrap().elements();
|
||||
return map_known_len(keys.into_iter(), func);
|
||||
} else if cls.is(self.ctx.types.frozenset_type) {
|
||||
let keys = value.downcast_ref::<PyFrozenSet>().unwrap().elements();
|
||||
return map_known_len(keys.into_iter(), func);
|
||||
} else if cls.is(self.ctx.types.dict_type) {
|
||||
let keys = value.downcast_ref::<PyDict>().unwrap().keys_vec();
|
||||
return map_known_len(keys.into_iter(), func);
|
||||
|
||||
+10
-3
@@ -113,6 +113,14 @@ fn shell_exec(
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_stdio(vm: &VirtualMachine) {
|
||||
for name in ["stdout", "stderr"] {
|
||||
if let Ok(stream) = vm.sys_module.get_attr(name, vm) {
|
||||
let _ = vm.call_method(&stream, "flush", ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enter a repl loop
|
||||
pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
|
||||
let mut repl = Readline::new(helper::ShellHelper::new(vm, scope.globals.clone()));
|
||||
@@ -128,9 +136,7 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
|
||||
None => ".repl_history.txt".into(),
|
||||
};
|
||||
|
||||
if repl.load_history(&repl_history_path).is_err() {
|
||||
println!("No previous history.");
|
||||
}
|
||||
let _ = repl.load_history(&repl_history_path);
|
||||
|
||||
// We might either be waiting to know if a block is complete, or waiting to know if a multiline
|
||||
// statement is complete. In the former case, we need to ensure that we read one extra new line
|
||||
@@ -245,6 +251,7 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
|
||||
}
|
||||
vm.print_exception(exc);
|
||||
}
|
||||
flush_stdio(vm);
|
||||
}
|
||||
repl.save_history(&repl_history_path).unwrap();
|
||||
|
||||
|
||||
Reference in new issue
Block a user