From 10cb49430a1f9c73f6ebdce69a982cd470904639 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:47:12 +0900 Subject: [PATCH 1/4] Detach during blocking Windows multiprocessing socket I/O (#9093) Snapshot locked send buffers before waiting and preserve the host-captured Winsock error across VM reattachment. Assisted-by: OpenAI Codex: model identifier unavailable Co-authored-by: dot --- crates/stdlib/src/multiprocessing.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs index c992f90f7..c9a079039 100644 --- a/crates/stdlib/src/multiprocessing.rs +++ b/crates/stdlib/src/multiprocessing.rs @@ -408,19 +408,22 @@ mod _multiprocessing { #[pyfunction] fn recv(socket: usize, size: usize, vm: &VirtualMachine) -> PyResult> { - let mut buf = - host_multiprocessing::recv_socket(socket as host_multiprocessing::RawSocket, size) - .map_err(|_| vm.new_last_os_error())?; + let mut buf = vm + .allow_threads(|| { + host_multiprocessing::recv_socket(socket as host_multiprocessing::RawSocket, size) + }) + .map_err(|error| error.to_pyexception(vm))?; buf.shrink_to_fit(); Ok(buf) } #[pyfunction] fn send(socket: usize, buf: ArgBytesLike, vm: &VirtualMachine) -> PyResult { - buf.with_ref(|b| { - host_multiprocessing::send_socket(socket as host_multiprocessing::RawSocket, b) + let buf = buf.borrow_buf_unlocked(vm)?; + vm.allow_threads(|| { + host_multiprocessing::send_socket(socket as host_multiprocessing::RawSocket, &buf) }) - .map_err(|_| vm.new_last_os_error()) + .map_err(|error| error.to_pyexception(vm)) } } From 7c3c7a73c705c6581c24c8027cb27874bd195368 Mon Sep 17 00:00:00 2001 From: Jiwoo Ahn Date: Tue, 13 Oct 2026 20:47:53 +0900 Subject: [PATCH 2/4] ci: run all passing extra_tests snippets on wasm-wasi via exclude list (#9083) - Problem: WASM_WASI_SNIPPETS only declared 2 of 244 extra_tests/snippets files, so every newly-fixed snippet needed a manual ci.yaml edit to gain wasm-wasi coverage - Fix: loop over extra_tests/snippets/*.py directly and skip only the 57 files that still fail, hang, or are intentionally-failing (WASM_WASI_SNIPPET_EXCLUDE), so fixed and future snippets run automatically - Verified: built wasm32-wasip1 from current upstream/main, ran all 244 snippets; the 187 non-excluded files pass 3x repeated runs with no flakiness (~48s total, well under the 30-minute job budget); the 57 excluded files were categorized (missing modules, threading disabled in this build, 32-bit overflow, no process support on WASI, wasm call-stack limits, socket stub, sandbox path issues, and a couple of individual bugs) Assisted-by: Claude Code:claude-sonnet-5 Signed-off-by: Jiwoo Ahn --- .github/workflows/ci.yaml | 81 +++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c48bd99a1..8f8783da1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -77,6 +77,7 @@ jobs: ':examples/**' \ ':benches/**' \ ':example_projects/**' \ + ':extra_tests/snippets/**' \ ':.github/workflows/ci.yaml' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" @@ -1009,10 +1010,71 @@ jobs: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true RUN_WASM_WASI: ${{ needs.determine_changes.outputs.rust_code == 'true' || github.ref == 'refs/heads/main' }} - # Passing snippets under wasm32-wasip1. - WASM_WASI_SNIPPETS: >- - stdlib_random - stdlib_time + # extra_tests/snippets files that still fail or hang under wasm32-wasip1; + # every other snippet is discovered and run automatically. + WASM_WASI_SNIPPET_EXCLUDE: >- + builtin_bytearray + builtin_bytes + builtin_hash + builtin_int + builtin_iter + builtin_list + builtin_map + builtin_memoryview + builtin_open + builtin_posixshmem + builtin_slice + builtin_str + builtin_thread + cli_int_max_str_digits + protocol_index_argument + recursion + stdlib_ast + stdlib_asyncio + stdlib_collections_deque + stdlib_contextvars + stdlib_ctypes + stdlib_ctypes_byvalue + stdlib_ctypes_calls + stdlib_ctypes_from_buffer + stdlib_genericalias + stdlib_io + stdlib_io_blocking_buffer + stdlib_itertools + stdlib_json + stdlib_mmap + stdlib_multiprocessing_condition + stdlib_multiprocessing_fork_sharer + stdlib_multiprocessing_sharedctypes + stdlib_os + stdlib_pwd + stdlib_queue_signal + stdlib_select + stdlib_socket + stdlib_sqlite + stdlib_ssl + stdlib_ssl_short_recv + stdlib_struct + stdlib_subinterpreters + stdlib_subprocess + stdlib_sys + stdlib_sys_audit + stdlib_threading_current_frames + stdlib_threading_gc_frame_race + stdlib_threading_gc_import + stdlib_threading_generator + stdlib_threading_set_repr + stdlib_threading_type_cache + stdlib_thread_native_id + stdlib_types + stdlib_typing + stdlib_urllib_https_misaligned_recv + stdlib_zoneinfo_cache + syntax_async + syntax_async_comprehension + syntax_deep_nesting + vm_frame_locals + xfail_assert # Passing Lib/test files under wasm32-wasip1 WASM_WASI_TEST_FILES: >- test_int @@ -1075,9 +1137,14 @@ jobs: - name: run snippets if: env.RUN_WASM_WASI == 'true' run: | - for name in $WASM_WASI_SNIPPETS; do - echo "::group::extra_tests/snippets/${name}.py" - wasmer run --volume "$(pwd)" target/wasm32-wasip1/wasm-release/rustpython.wasm -- "$(pwd)/extra_tests/snippets/${name}.py" + exclude=" $WASM_WASI_SNIPPET_EXCLUDE " + for path in extra_tests/snippets/*.py; do + name=$(basename "$path" .py) + case "$exclude" in + *" $name "*) continue ;; + esac + echo "::group::${path}" + wasmer run --volume "$(pwd)" target/wasm32-wasip1/wasm-release/rustpython.wasm -- "$(pwd)/${path}" echo "::endgroup::" done - name: run cpython unittest From 0803a55df573e8db5ce8aa941d0562df1161194b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:50:13 +0900 Subject: [PATCH 3/4] Convert native base references without consulting the Python class (#9090) PyRef::into_base and Py::to_base cast the pointer directly instead of going through a class-based downcast. into_base no longer reaches unreachable_unchecked when the object's MRO omits the native base, and to_base drops the equivalent debug assertion. Extracted from #8963. Assisted-by: Codex:gpt-6 Assisted-by: Claude:claude-opus-5-5 Co-authored-by: 1ndahous3 <9205230+1ndahous3@users.noreply.github.com> --- crates/vm/src/object/core.rs | 48 ++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index d22d6ef74..9cd52c32d 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -2904,16 +2904,14 @@ where T::Base: core::fmt::Debug, { /// Converts this reference to the base type (ownership transfer). - /// # Safety - /// T and T::Base must have compatible layouts in size_of::() bytes. #[inline] #[must_use] pub fn into_base(self) -> PyRef { - let obj: PyObjectRef = self.into(); - match obj.downcast() { - Ok(base_ref) => base_ref, - Err(_) => unsafe { core::hint::unreachable_unchecked() }, - } + // PySubclass establishes the physical prefix; Python may have changed + // the object's class or MRO since this typed reference was constructed. + let ptr = self.ptr.cast(); + core::mem::forget(self); + PyRef { ptr } } #[inline] #[must_use] @@ -2934,9 +2932,8 @@ impl Py { /// Converts `&Py` to `&Py`. #[inline] pub fn to_base(&self) -> &Py { - debug_assert!(self.as_object().downcast_ref::().is_some()); - // SAFETY: T is #[repr(transparent)] over T::Base, - // so Py and Py have the same layout. + // SAFETY: PySubclass guarantees a valid base prefix with matching + // payload offsets and compatible object alignment. unsafe { &*(self as *const Self as *const Py) } } @@ -3410,6 +3407,37 @@ mod tests { } } + #[test] + fn native_base_conversions_ignore_python_mro() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let class = vm.ctx.new_class( + None, + "HiddenOSErrorBase", + vm.ctx.exceptions.os_error.to_owned(), + Default::default(), + ); + let marker: PyObjectRef = vm.ctx.new_int(42).into(); + let error = vm.new_os_subtype_error(class.clone(), None, marker.clone()); + // A custom MRO can omit native bases without changing the allocation. + *class.mro.write() = vec![class.clone(), vm.ctx.types.object_type.to_owned()]; + assert!( + !error + .as_object() + .downcastable::() + ); + + assert!(error.to_base().to_base().get_arg(0).unwrap().is(&marker)); + assert!( + error + .into_base() + .into_base() + .get_arg(0) + .unwrap() + .is(&marker) + ); + }); + } + #[test] fn native_type_basicsize_includes_payload_padding() { use crate::class::PyClassDef; From 723dee027eb119a50f13c41d60e40e9b36afca41 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:58:28 +0900 Subject: [PATCH 4/4] Initialize PyRef marker in native base conversion (#9096) Fix the missing field in the new into_base initializer without changing its pointer or ownership transfer. Assisted-by: OpenAI Codex: model identifier unavailable Co-authored-by: dot --- crates/vm/src/object/core.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 9cd52c32d..8dacc13d3 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -2911,7 +2911,10 @@ where // the object's class or MRO since this typed reference was constructed. let ptr = self.ptr.cast(); core::mem::forget(self); - PyRef { ptr } + PyRef { + ptr, + marker: PhantomData, + } } #[inline] #[must_use]