From 8b1460088e212aeeb0aeb34d60c0cb7f22a0be1e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 5 Dec 2025 19:59:01 +0900 Subject: [PATCH 1/4] mmap on windows --- Lib/test/test_mmap.py | 2 + crates/stdlib/Cargo.toml | 13 +- crates/stdlib/src/lib.rs | 5 +- crates/stdlib/src/mmap.rs | 426 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 424 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index ed96be53cca..f80df63bb86 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -618,6 +618,7 @@ def test_non_ascii_byte(self): self.assertEqual(m.read_byte(), b) m.close() + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_tagname(self): data1 = b"0123456789" @@ -867,6 +868,7 @@ def test_resize_fails_if_mapping_held_elsewhere(self): finally: f.close() + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_resize_succeeds_with_error_for_second_named_mapping(self): """If a more than one mapping exists of the same name, none of them can diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index dbff752b99c..13c1eb2e60b 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -97,18 +97,16 @@ chrono.workspace = true mac_address = "1.1.3" uuid = { version = "1.1.2", features = ["v1"] } -# mmap -[target.'cfg(all(unix, not(target_arch = "wasm32")))'.dependencies] -memmap2 = "0.5.10" -page_size = "0.6" - [target.'cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))'.dependencies] termios = "0.3.3" [target.'cfg(unix)'.dependencies] rustix = { workspace = true } +# mmap + socket dependencies [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +memmap2 = "0.5.10" +page_size = "0.6" gethostname = "1.0.2" socket2 = { version = "0.6.0", features = ["all"] } dns-lookup = "3.0" @@ -146,12 +144,15 @@ widestring = { workspace = true } [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ + "Win32_Foundation", "Win32_Networking_WinSock", "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Security_Cryptography", + "Win32_Storage_FileSystem", "Win32_System_Environment", - "Win32_System_IO" + "Win32_System_IO", + "Win32_System_Threading" ] [target.'cfg(target_os = "macos")'.dependencies] diff --git a/crates/stdlib/src/lib.rs b/crates/stdlib/src/lib.rs index 01a27b76609..c9b5ca32b57 100644 --- a/crates/stdlib/src/lib.rs +++ b/crates/stdlib/src/lib.rs @@ -36,7 +36,7 @@ mod json; mod locale; mod math; -#[cfg(unix)] +#[cfg(any(unix, windows))] mod mmap; mod opcode; mod pyexpat; @@ -189,6 +189,9 @@ pub fn get_module_inits() -> impl Iterator, StdlibInit #[cfg(unix)] { "_posixsubprocess" => posixsubprocess::make_module, + } + #[cfg(any(unix, windows))] + { "mmap" => mmap::make_module, } #[cfg(all(unix, not(target_os = "redox")))] diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 143bdcb43d7..68b560acd78 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -22,15 +22,34 @@ mod mmap { types::{AsBuffer, AsMapping, AsSequence, Constructor, Representable}, }; use crossbeam_utils::atomic::AtomicCell; - use memmap2::{Advice, Mmap, MmapMut, MmapOptions}; + #[cfg(unix)] + use memmap2::Advice; + use memmap2::{Mmap, MmapMut, MmapOptions}; #[cfg(unix)] use nix::sys::stat::fstat; + #[cfg(unix)] use nix::unistd; use num_traits::Signed; + #[cfg(unix)] use rustpython_common::crt_fd; + #[cfg(windows)] + use rustpython_common::suppress_iph; use std::io::{self, Write}; use std::ops::{Deref, DerefMut}; + #[cfg(windows)] + use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; + #[cfg(windows)] + use windows_sys::Win32::Foundation::{ + CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE, + }; + #[cfg(windows)] + use windows_sys::Win32::Storage::FileSystem::{ + GetFileSize, SetEndOfFile, SetFilePointerEx, FILE_BEGIN, + }; + #[cfg(windows)] + use windows_sys::Win32::System::Threading::GetCurrentProcess; + #[cfg(unix)] fn advice_try_from_i32(vm: &VirtualMachine, i: i32) -> PyResult { Ok(match i { libc::MADV_NORMAL => Advice::Normal, @@ -86,6 +105,7 @@ mod mmap { } } + #[cfg(unix)] #[pyattr] use libc::{ MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, MAP_ANON, @@ -148,13 +168,13 @@ mod mmap { #[pyattr] const ACCESS_COPY: u32 = AccessMode::Copy as u32; - #[cfg(all(unix, not(target_arch = "wasm32")))] + #[cfg(not(target_arch = "wasm32"))] #[pyattr(name = "PAGESIZE", once)] fn page_size(_vm: &VirtualMachine) -> usize { page_size::get() } - #[cfg(all(unix, not(target_arch = "wasm32")))] + #[cfg(not(target_arch = "wasm32"))] #[pyattr(name = "ALLOCATIONGRANULARITY", once)] fn granularity(_vm: &VirtualMachine) -> usize { page_size::get_granularity() @@ -177,28 +197,67 @@ mod mmap { struct PyMmap { closed: AtomicCell, mmap: PyMutex>, + #[cfg(unix)] fd: AtomicCell, - offset: libc::off_t, + #[cfg(windows)] + handle: AtomicCell, // HANDLE is isize on Windows + offset: i64, size: AtomicCell, pos: AtomicCell, // relative to offset exports: AtomicCell, access: AccessMode, } + impl Drop for PyMmap { + fn drop(&mut self) { + // Close the file handle/descriptor if not already closed + #[cfg(unix)] + { + let fd = self.fd.swap(-1); + if fd >= 0 { + unsafe { libc::close(fd) }; + } + } + #[cfg(windows)] + { + let handle = self.handle.swap(INVALID_HANDLE_VALUE as isize); + if handle != INVALID_HANDLE_VALUE as isize { + unsafe { CloseHandle(handle as HANDLE) }; + } + } + } + } + + #[cfg(unix)] #[derive(FromArgs)] struct MmapNewArgs { #[pyarg(any)] fileno: i32, #[pyarg(any)] length: isize, - #[pyarg(any, default = MAP_SHARED)] + #[pyarg(any, default = libc::MAP_SHARED)] flags: libc::c_int, - #[pyarg(any, default = PROT_WRITE|PROT_READ)] + #[pyarg(any, default = libc::PROT_WRITE | libc::PROT_READ)] prot: libc::c_int, #[pyarg(any, default = AccessMode::Default)] access: AccessMode, #[pyarg(any, default = 0)] - offset: libc::off_t, + offset: i64, + } + + #[cfg(windows)] + #[derive(FromArgs)] + struct MmapNewArgs { + #[pyarg(any)] + fileno: i32, + #[pyarg(any)] + length: isize, + #[pyarg(any, default)] + tagname: Option, + #[pyarg(any, default = AccessMode::Default)] + access: AccessMode, + #[pyarg(any, default = 0)] + offset: i64, } #[derive(FromArgs)] @@ -241,7 +300,7 @@ mod mmap { end: Option, } - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] #[derive(FromArgs)] pub struct AdviseOptions { #[pyarg(positional)] @@ -252,7 +311,7 @@ mod mmap { length: Option, } - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] impl AdviseOptions { fn values(self, len: usize, vm: &VirtualMachine) -> PyResult<(libc::c_int, usize, usize)> { let start = self @@ -291,7 +350,6 @@ mod mmap { impl Constructor for PyMmap { type Args = MmapNewArgs; - // TODO: Windows is not supported right now. #[cfg(unix)] fn py_new( cls: PyTypeRef, @@ -305,6 +363,8 @@ mod mmap { }: Self::Args, vm: &VirtualMachine, ) -> PyResult { + use libc::{MAP_PRIVATE, MAP_SHARED, PROT_READ, PROT_WRITE}; + let map_size = length; if map_size < 0 { return Err(vm.new_overflow_error("memory mapped length must be positive")); @@ -321,7 +381,7 @@ mod mmap { return Err(vm.new_value_error("mmap can't specify both access and flags, prot.")); } - // TODO: memmap2 doesn't support mapping with pro and flags right now + // TODO: memmap2 doesn't support mapping with prot and flags right now let (_flags, _prot, access) = match access { AccessMode::Read => (MAP_SHARED, PROT_READ, access), AccessMode::Write => (MAP_SHARED, PROT_READ | PROT_WRITE, access), @@ -356,13 +416,13 @@ mod mmap { map_size = (file_len - offset) .try_into() .map_err(|_| vm.new_value_error("mmap length is too large"))?; - } else if offset > file_len || file_len - offset < map_size as libc::off_t { + } else if offset > file_len || file_len - offset < map_size as i64 { return Err(vm.new_value_error("mmap length is greater than file size")); } } let mut mmap_opt = MmapOptions::new(); - let mmap_opt = mmap_opt.offset(offset.try_into().unwrap()).len(map_size); + let mmap_opt = mmap_opt.offset(offset as u64).len(map_size); let (fd, mmap) = || -> std::io::Result<_> { if let Ok(fd) = fd { @@ -395,6 +455,167 @@ mod mmap { m_obj.into_ref_with_type(vm, cls).map(Into::into) } + + #[cfg(windows)] + fn py_new( + cls: PyTypeRef, + MmapNewArgs { + fileno, + length, + tagname: _tagname, // TODO: implement named mappings + access, + offset, + }: Self::Args, + vm: &VirtualMachine, + ) -> PyResult { + let map_size = length; + if map_size < 0 { + return Err(vm.new_overflow_error("memory mapped length must be positive")); + } + let mut map_size = map_size as usize; + + if offset < 0 { + return Err(vm.new_overflow_error("memory mapped offset must be positive")); + } + + // Get file handle from fileno + // fileno -1 or 0 means anonymous mapping + let fh: Option = if fileno != -1 && fileno != 0 { + // Convert CRT file descriptor to Windows HANDLE + // Use suppress_iph! to avoid crashes when the fd is invalid. + // This is critical because socket fds wrapped via _open_osfhandle + // may cause crashes in _get_osfhandle on Windows. + // See Python bug https://bugs.python.org/issue30114 + let handle = unsafe { suppress_iph!(libc::get_osfhandle(fileno)) }; + // Check for invalid handle value (-1 on Windows) + if handle == -1 || handle == INVALID_HANDLE_VALUE as isize { + return Err( + vm.new_os_error(format!("Invalid file descriptor: {}", fileno)) + ); + } + Some(handle as HANDLE) + } else { + None + }; + + // Get file size if we have a file handle and map_size is 0 + let mut duplicated_handle: HANDLE = INVALID_HANDLE_VALUE; + if let Some(fh) = fh { + // Duplicate handle so Python code can close the original + let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; + let result = unsafe { + DuplicateHandle( + GetCurrentProcess(), + fh, + GetCurrentProcess(), + &mut new_handle, + 0, + 0, // not inheritable + DUPLICATE_SAME_ACCESS, + ) + }; + if result == 0 { + return Err(io::Error::last_os_error().to_pyexception(vm)); + } + duplicated_handle = new_handle; + + // Get file size + let mut high: u32 = 0; + let low = unsafe { GetFileSize(fh, &mut high) }; + if low == u32::MAX { + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(0) { + unsafe { CloseHandle(duplicated_handle) }; + return Err(err.to_pyexception(vm)); + } + } + let file_len = ((high as i64) << 32) | (low as i64); + + if map_size == 0 { + if file_len == 0 { + unsafe { CloseHandle(duplicated_handle) }; + return Err(vm.new_value_error("cannot mmap an empty file")); + } + if offset >= file_len { + unsafe { CloseHandle(duplicated_handle) }; + return Err(vm.new_value_error("mmap offset is greater than file size")); + } + if file_len - offset > isize::MAX as i64 { + unsafe { CloseHandle(duplicated_handle) }; + return Err(vm.new_value_error("mmap length is too large")); + } + map_size = (file_len - offset) as usize; + } else { + // If map_size > file_len, extend the file (Windows behavior) + let required_size = offset + map_size as i64; + if required_size > file_len { + // Extend file using SetFilePointerEx + SetEndOfFile + let result = unsafe { + SetFilePointerEx( + duplicated_handle, + required_size, + std::ptr::null_mut(), + FILE_BEGIN, + ) + }; + if result == 0 { + let err = io::Error::last_os_error(); + unsafe { CloseHandle(duplicated_handle) }; + return Err(err.to_pyexception(vm)); + } + let result = unsafe { SetEndOfFile(duplicated_handle) }; + if result == 0 { + let err = io::Error::last_os_error(); + unsafe { CloseHandle(duplicated_handle) }; + return Err(err.to_pyexception(vm)); + } + } + } + } + + let mut mmap_opt = MmapOptions::new(); + let mmap_opt = mmap_opt.offset(offset as u64).len(map_size); + + let (handle, mmap) = if duplicated_handle != INVALID_HANDLE_VALUE { + // Safety: We just duplicated this handle and it's valid + let owned_handle = + unsafe { OwnedHandle::from_raw_handle(duplicated_handle as RawHandle) }; + + let mmap_result = match access { + AccessMode::Default | AccessMode::Write => { + unsafe { mmap_opt.map_mut(&owned_handle) }.map(MmapObj::Write) + } + AccessMode::Read => unsafe { mmap_opt.map(&owned_handle) }.map(MmapObj::Read), + AccessMode::Copy => { + unsafe { mmap_opt.map_copy(&owned_handle) }.map(MmapObj::Write) + } + }; + + let mmap = mmap_result.map_err(|e| e.to_pyexception(vm))?; + + // Keep the handle alive + let raw = owned_handle.as_raw_handle() as isize; + std::mem::forget(owned_handle); + (raw, mmap) + } else { + // Anonymous mapping + let mmap = mmap_opt.map_anon().map_err(|e| e.to_pyexception(vm))?; + (INVALID_HANDLE_VALUE as isize, MmapObj::Write(mmap)) + }; + + let m_obj = Self { + closed: AtomicCell::new(false), + mmap: PyMutex::new(Some(mmap)), + handle: AtomicCell::new(handle), + offset, + size: AtomicCell::new(map_size), + pos: AtomicCell::new(0), + exports: AtomicCell::new(0), + access, + }; + + m_obj.into_ref_with_type(vm, cls).map(Into::into) + } } static BUFFER_METHODS: BufferMethods = BufferMethods { @@ -566,6 +787,22 @@ mod mmap { self.closed.store(true); *mmap = None; + // Close the file handle/descriptor + #[cfg(unix)] + { + let fd = self.fd.swap(-1); + if fd >= 0 { + unsafe { libc::close(fd) }; + } + } + #[cfg(windows)] + { + let handle = self.handle.swap(INVALID_HANDLE_VALUE as isize); + if handle != INVALID_HANDLE_VALUE as isize { + unsafe { CloseHandle(handle as HANDLE) }; + } + } + Ok(()) } @@ -642,7 +879,7 @@ mod mmap { Ok(()) } - #[cfg(not(target_os = "redox"))] + #[cfg(all(unix, not(target_os = "redox")))] #[allow(unused_assignments)] #[pymethod] fn madvise(&self, options: AdviseOptions, vm: &VirtualMachine) -> PyResult<()> { @@ -792,13 +1029,120 @@ mod mmap { Ok(result) } - // TODO: supports resize + #[cfg(unix)] #[pymethod] fn resize(&self, _newsize: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { self.check_resizeable(vm)?; + // TODO: implement using mremap on Linux Err(vm.new_system_error("mmap: resizing not available--no mremap()")) } + #[cfg(windows)] + #[pymethod] + fn resize(&self, newsize: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + self.check_resizeable(vm)?; + + let newsize: usize = newsize + .try_to_primitive(vm) + .map_err(|_| vm.new_value_error("new size out of range"))?; + + if newsize == 0 { + return Err(vm.new_value_error("new size must be positive")); + } + + let handle = self.handle.load(); + let is_anonymous = handle == INVALID_HANDLE_VALUE as isize; + + // Get the lock on mmap + let mut mmap_guard = self.mmap.lock(); + + if is_anonymous { + // For anonymous mmap, we need to: + // 1. Create a new anonymous mmap with the new size + // 2. Copy data from old mmap to new mmap + // 3. Replace the old mmap + + let old_size = self.size.load(); + let copy_size = std::cmp::min(old_size, newsize); + + // Create new anonymous mmap + let mut new_mmap_opts = MmapOptions::new(); + let new_mmap = new_mmap_opts + .len(newsize) + .map_anon() + .map_err(|e| e.to_pyexception(vm))?; + + // Copy data from old mmap to new mmap + if let Some(old_mmap) = mmap_guard.as_ref() { + let src = match old_mmap { + MmapObj::Write(m) => &m[..copy_size], + MmapObj::Read(m) => &m[..copy_size], + }; + // Safety: we just created new_mmap and know it's large enough + unsafe { + std::ptr::copy_nonoverlapping( + src.as_ptr(), + new_mmap.as_ptr() as *mut u8, + copy_size, + ); + } + } + + *mmap_guard = Some(MmapObj::Write(new_mmap)); + self.size.store(newsize); + } else { + // File-backed mmap resize + + // Drop the current mmap to release the file mapping + *mmap_guard = None; + + // Resize the file + let required_size = self.offset + newsize as i64; + let result = unsafe { + SetFilePointerEx( + handle as HANDLE, + required_size, + std::ptr::null_mut(), + FILE_BEGIN, + ) + }; + if result == 0 { + // Restore original mmap on error + let err = io::Error::last_os_error(); + let old_size = self.size.load(); + if let Ok(mmap) = Self::create_mmap_windows(handle as HANDLE, self.offset, old_size, &self.access) { + *mmap_guard = Some(mmap); + } + return Err(err.to_pyexception(vm)); + } + + let result = unsafe { SetEndOfFile(handle as HANDLE) }; + if result == 0 { + let err = io::Error::last_os_error(); + let old_size = self.size.load(); + if let Ok(mmap) = Self::create_mmap_windows(handle as HANDLE, self.offset, old_size, &self.access) { + *mmap_guard = Some(mmap); + } + return Err(err.to_pyexception(vm)); + } + + // Create new mmap with the new size + let new_mmap = Self::create_mmap_windows(handle as HANDLE, self.offset, newsize, &self.access) + .map_err(|e| e.to_pyexception(vm))?; + + *mmap_guard = Some(new_mmap); + self.size.store(newsize); + } + + // Adjust position if it's beyond the new size + let pos = self.pos.load(); + if pos > newsize { + self.pos.store(newsize); + } + + Ok(()) + } + #[pymethod] fn seek( &self, @@ -838,6 +1182,7 @@ mod mmap { Ok(()) } + #[cfg(unix)] #[pymethod] fn size(&self, vm: &VirtualMachine) -> std::io::Result { let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(self.fd.load())? }; @@ -845,6 +1190,27 @@ mod mmap { Ok(PyInt::from(file_len).into_ref(&vm.ctx)) } + #[cfg(windows)] + #[pymethod] + fn size(&self, vm: &VirtualMachine) -> PyResult { + let handle = self.handle.load(); + if handle == INVALID_HANDLE_VALUE as isize { + // Anonymous mapping, return the mmap size + return Ok(PyInt::from(self.__len__()).into_ref(&vm.ctx)); + } + + let mut high: u32 = 0; + let low = unsafe { GetFileSize(handle as HANDLE, &mut high) }; + if low == u32::MAX { + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(0) { + return Err(err.to_pyexception(vm)); + } + } + let file_len = ((high as i64) << 32) | (low as i64); + Ok(PyInt::from(file_len).into_ref(&vm.ctx)) + } + #[pymethod] fn tell(&self) -> PyResult { Ok(self.pos()) @@ -921,6 +1287,36 @@ mod mmap { } impl PyMmap { + #[cfg(windows)] + fn create_mmap_windows( + handle: HANDLE, + offset: i64, + size: usize, + access: &AccessMode, + ) -> io::Result { + use std::fs::File; + + // Create an owned handle wrapper for memmap2 + // We need to create a File from the handle + let file = unsafe { File::from_raw_handle(handle as RawHandle) }; + + let mut mmap_opt = MmapOptions::new(); + let mmap_opt = mmap_opt.offset(offset as u64).len(size); + + let result = match access { + AccessMode::Default | AccessMode::Write => { + MmapObj::Write(unsafe { mmap_opt.map_mut(&file) }?) + } + AccessMode::Read => MmapObj::Read(unsafe { mmap_opt.map(&file) }?), + AccessMode::Copy => MmapObj::Write(unsafe { mmap_opt.map_copy(&file) }?), + }; + + // Don't close the file handle - we're borrowing it + std::mem::forget(file); + + Ok(result) + } + fn getitem_by_index(&self, i: isize, vm: &VirtualMachine) -> PyResult { let i = i .wrapped_at(self.__len__()) From 145ec958dfa9e6698e7fd615cbfd70ef79130569 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 5 Dec 2025 22:27:50 +0900 Subject: [PATCH 2/4] clean up --- crates/stdlib/src/mmap.rs | 302 ++++++++++++++++---------------------- 1 file changed, 127 insertions(+), 175 deletions(-) diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index 68b560acd78..d21acc62941 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -22,65 +22,52 @@ mod mmap { types::{AsBuffer, AsMapping, AsSequence, Constructor, Representable}, }; use crossbeam_utils::atomic::AtomicCell; - #[cfg(unix)] - use memmap2::Advice; use memmap2::{Mmap, MmapMut, MmapOptions}; - #[cfg(unix)] - use nix::sys::stat::fstat; - #[cfg(unix)] - use nix::unistd; use num_traits::Signed; + use std::io::{self, Write}; + use std::ops::{Deref, DerefMut}; + + #[cfg(unix)] + use nix::{sys::stat::fstat, unistd}; #[cfg(unix)] use rustpython_common::crt_fd; + #[cfg(windows)] use rustpython_common::suppress_iph; - use std::io::{self, Write}; - use std::ops::{Deref, DerefMut}; #[cfg(windows)] use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; #[cfg(windows)] - use windows_sys::Win32::Foundation::{ - CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE, - }; - #[cfg(windows)] - use windows_sys::Win32::Storage::FileSystem::{ - GetFileSize, SetEndOfFile, SetFilePointerEx, FILE_BEGIN, + use windows_sys::Win32::{ + Foundation::{ + CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE, + }, + Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx}, + System::Threading::GetCurrentProcess, }; - #[cfg(windows)] - use windows_sys::Win32::System::Threading::GetCurrentProcess; #[cfg(unix)] - fn advice_try_from_i32(vm: &VirtualMachine, i: i32) -> PyResult { - Ok(match i { - libc::MADV_NORMAL => Advice::Normal, - libc::MADV_RANDOM => Advice::Random, - libc::MADV_SEQUENTIAL => Advice::Sequential, - libc::MADV_WILLNEED => Advice::WillNeed, - libc::MADV_DONTNEED => Advice::DontNeed, + fn validate_advice(vm: &VirtualMachine, advice: i32) -> PyResult { + match advice { + libc::MADV_NORMAL + | libc::MADV_RANDOM + | libc::MADV_SEQUENTIAL + | libc::MADV_WILLNEED + | libc::MADV_DONTNEED => Ok(advice), #[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios"))] - libc::MADV_FREE => Advice::Free, - #[cfg(target_os = "linux")] - libc::MADV_DONTFORK => Advice::DontFork, - #[cfg(target_os = "linux")] - libc::MADV_DOFORK => Advice::DoFork, - #[cfg(target_os = "linux")] - libc::MADV_MERGEABLE => Advice::Mergeable, - #[cfg(target_os = "linux")] - libc::MADV_UNMERGEABLE => Advice::Unmergeable, + libc::MADV_FREE => Ok(advice), #[cfg(target_os = "linux")] - libc::MADV_HUGEPAGE => Advice::HugePage, - #[cfg(target_os = "linux")] - libc::MADV_NOHUGEPAGE => Advice::NoHugePage, - #[cfg(target_os = "linux")] - libc::MADV_REMOVE => Advice::Remove, - #[cfg(target_os = "linux")] - libc::MADV_DONTDUMP => Advice::DontDump, - #[cfg(target_os = "linux")] - libc::MADV_DODUMP => Advice::DoDump, - #[cfg(target_os = "linux")] - libc::MADV_HWPOISON => Advice::HwPoison, - _ => return Err(vm.new_value_error("Not a valid Advice value")), - }) + libc::MADV_DONTFORK + | libc::MADV_DOFORK + | libc::MADV_MERGEABLE + | libc::MADV_UNMERGEABLE + | libc::MADV_HUGEPAGE + | libc::MADV_NOHUGEPAGE + | libc::MADV_REMOVE + | libc::MADV_DONTDUMP + | libc::MADV_DODUMP + | libc::MADV_HWPOISON => Ok(advice), + _ => Err(vm.new_value_error("Not a valid Advice value")), + } } #[repr(C)] @@ -191,6 +178,15 @@ mod mmap { Read(Mmap), } + impl MmapObj { + fn as_slice(&self) -> &[u8] { + match self { + MmapObj::Read(mmap) => &mmap[..], + MmapObj::Write(mmap) => &mmap[..], + } + } + } + #[pyattr] #[pyclass(name = "mmap")] #[derive(Debug, PyPayload)] @@ -208,9 +204,9 @@ mod mmap { access: AccessMode, } - impl Drop for PyMmap { - fn drop(&mut self) { - // Close the file handle/descriptor if not already closed + impl PyMmap { + /// Close the underlying file handle/descriptor if open + fn close_handle(&self) { #[cfg(unix)] { let fd = self.fd.swap(-1); @@ -228,6 +224,12 @@ mod mmap { } } + impl Drop for PyMmap { + fn drop(&mut self) { + self.close_handle(); + } + } + #[cfg(unix)] #[derive(FromArgs)] struct MmapNewArgs { @@ -253,6 +255,7 @@ mod mmap { #[pyarg(any)] length: isize, #[pyarg(any, default)] + #[allow(dead_code)] tagname: Option, #[pyarg(any, default = AccessMode::Default)] access: AccessMode, @@ -260,6 +263,19 @@ mod mmap { offset: i64, } + impl MmapNewArgs { + /// Validate mmap constructor arguments + fn validate_new_args(&self, vm: &VirtualMachine) -> PyResult { + if self.length < 0 { + return Err(vm.new_overflow_error("memory mapped length must be positive")); + } + if self.offset < 0 { + return Err(vm.new_overflow_error("memory mapped offset must be positive")); + } + Ok(self.length as usize) + } + } + #[derive(FromArgs)] pub struct FlushOptions { #[pyarg(positional, default)] @@ -351,29 +367,18 @@ mod mmap { type Args = MmapNewArgs; #[cfg(unix)] - fn py_new( - cls: PyTypeRef, - MmapNewArgs { + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + use libc::{MAP_PRIVATE, MAP_SHARED, PROT_READ, PROT_WRITE}; + + let mut map_size = args.validate_new_args(vm)?; + let MmapNewArgs { fileno: fd, - length, flags, prot, access, offset, - }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - use libc::{MAP_PRIVATE, MAP_SHARED, PROT_READ, PROT_WRITE}; - - let map_size = length; - if map_size < 0 { - return Err(vm.new_overflow_error("memory mapped length must be positive")); - } - let mut map_size = map_size as usize; - - if offset < 0 { - return Err(vm.new_overflow_error("memory mapped offset must be positive")); - } + .. + } = args; if (access != AccessMode::Default) && ((flags != MAP_SHARED) || (prot != (PROT_WRITE | PROT_READ))) @@ -402,7 +407,7 @@ mod mmap { if let Ok(fd) = fd { let metadata = fstat(fd) .map_err(|err| io::Error::from_raw_os_error(err as i32).to_pyexception(vm))?; - let file_len = metadata.st_size; + let file_len = metadata.st_size as i64; if map_size == 0 { if file_len == 0 { @@ -457,26 +462,14 @@ mod mmap { } #[cfg(windows)] - fn py_new( - cls: PyTypeRef, - MmapNewArgs { + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let mut map_size = args.validate_new_args(vm)?; + let MmapNewArgs { fileno, - length, - tagname: _tagname, // TODO: implement named mappings access, offset, - }: Self::Args, - vm: &VirtualMachine, - ) -> PyResult { - let map_size = length; - if map_size < 0 { - return Err(vm.new_overflow_error("memory mapped length must be positive")); - } - let mut map_size = map_size as usize; - - if offset < 0 { - return Err(vm.new_overflow_error("memory mapped offset must be positive")); - } + .. + } = args; // Get file handle from fileno // fileno -1 or 0 means anonymous mapping @@ -489,9 +482,7 @@ mod mmap { let handle = unsafe { suppress_iph!(libc::get_osfhandle(fileno)) }; // Check for invalid handle value (-1 on Windows) if handle == -1 || handle == INVALID_HANDLE_VALUE as isize { - return Err( - vm.new_os_error(format!("Invalid file descriptor: {}", fileno)) - ); + return Err(vm.new_os_error(format!("Invalid file descriptor: {}", fileno))); } Some(handle as HANDLE) } else { @@ -705,10 +696,7 @@ mod mmap { fn as_bytes(&self) -> BorrowedValue<'_, [u8]> { PyMutexGuard::map_immutable(self.mmap.lock(), |m| { - match m.as_ref().expect("mmap closed or invalid") { - MmapObj::Read(mmap) => &mmap[..], - MmapObj::Write(mmap) => &mmap[..], - } + m.as_ref().expect("mmap closed or invalid").as_slice() }) .into() } @@ -787,21 +775,7 @@ mod mmap { self.closed.store(true); *mmap = None; - // Close the file handle/descriptor - #[cfg(unix)] - { - let fd = self.fd.swap(-1); - if fd >= 0 { - unsafe { libc::close(fd) }; - } - } - #[cfg(windows)] - { - let handle = self.handle.swap(INVALID_HANDLE_VALUE as isize); - if handle != INVALID_HANDLE_VALUE as isize { - unsafe { CloseHandle(handle as HANDLE) }; - } - } + self.close_handle(); Ok(()) } @@ -830,10 +804,7 @@ mod mmap { } let mmap = self.check_valid(vm)?; - let buf = match mmap.as_ref().unwrap() { - MmapObj::Read(mmap) => &mmap[start..end], - MmapObj::Write(mmap) => &mmap[start..end], - }; + let buf = &mmap.as_ref().unwrap().as_slice()[start..end]; let pos = buf.windows(sub.len()).position(|window| window == sub); Ok(pos.map_or(PyInt::from(-1isize), |i| PyInt::from(start + i))) @@ -849,10 +820,7 @@ mod mmap { } let mmap = self.check_valid(vm)?; - let buf = match mmap.as_ref().unwrap() { - MmapObj::Read(mmap) => &mmap[start..end], - MmapObj::Write(mmap) => &mmap[start..end], - }; + let buf = &mmap.as_ref().unwrap().as_slice()[start..end]; let pos = buf.windows(sub.len()).rposition(|window| window == sub); Ok(pos.map_or(PyInt::from(-1isize), |i| PyInt::from(start + i))) @@ -880,18 +848,23 @@ mod mmap { } #[cfg(all(unix, not(target_os = "redox")))] - #[allow(unused_assignments)] #[pymethod] fn madvise(&self, options: AdviseOptions, vm: &VirtualMachine) -> PyResult<()> { let (option, _start, _length) = options.values(self.__len__(), vm)?; - let advice = advice_try_from_i32(vm, option)?; + let advice = validate_advice(vm, option)?; - //TODO: memmap2 doesn't support madvise range right now. - match self.check_valid(vm)?.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap.advise(advice), - MmapObj::Write(mmap) => mmap.advise(advice), + let guard = self.check_valid(vm)?; + let mmap = guard.deref().as_ref().unwrap(); + let (ptr, len) = match mmap { + MmapObj::Read(m) => (m.as_ptr(), m.len()), + MmapObj::Write(m) => (m.as_ptr(), m.len()), + }; + + // Call libc::madvise directly + let result = unsafe { libc::madvise(ptr as *mut libc::c_void, len, advice) }; + if result != 0 { + return Err(io::Error::last_os_error().to_pyexception(vm)); } - .map_err(|e| e.to_pyexception(vm))?; Ok(()) } @@ -965,10 +938,7 @@ mod mmap { .unwrap_or(remaining); let end_pos = pos + num_bytes; - let bytes = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[pos..end_pos].to_vec(), - MmapObj::Write(mmap) => mmap[pos..end_pos].to_vec(), - }; + let bytes = mmap.deref().as_ref().unwrap().as_slice()[pos..end_pos].to_vec(); let result = PyBytes::from(bytes).into_ref(&vm.ctx); @@ -984,10 +954,7 @@ mod mmap { return Err(vm.new_value_error("read byte out of range")); } - let b = match self.check_valid(vm)?.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[pos], - MmapObj::Write(mmap) => mmap[pos], - }; + let b = self.check_valid(vm)?.deref().as_ref().unwrap().as_slice()[pos]; self.advance_pos(1); @@ -1004,12 +971,8 @@ mod mmap { return Ok(PyBytes::from(vec![]).into_ref(&vm.ctx)); } - let eof = match mmap.as_ref().unwrap() { - MmapObj::Read(mmap) => &mmap[pos..], - MmapObj::Write(mmap) => &mmap[pos..], - } - .iter() - .position(|&x| x == b'\n'); + let slice = mmap.as_ref().unwrap().as_slice(); + let eof = slice[pos..].iter().position(|&x| x == b'\n'); let end_pos = if let Some(i) = eof { pos + i + 1 @@ -1017,10 +980,7 @@ mod mmap { self.__len__() }; - let bytes = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[pos..end_pos].to_vec(), - MmapObj::Write(mmap) => mmap[pos..end_pos].to_vec(), - }; + let bytes = slice[pos..end_pos].to_vec(); let result = PyBytes::from(bytes).into_ref(&vm.ctx); @@ -1067,7 +1027,7 @@ mod mmap { // Create new anonymous mmap let mut new_mmap_opts = MmapOptions::new(); - let new_mmap = new_mmap_opts + let mut new_mmap = new_mmap_opts .len(newsize) .map_anon() .map_err(|e| e.to_pyexception(vm))?; @@ -1078,14 +1038,7 @@ mod mmap { MmapObj::Write(m) => &m[..copy_size], MmapObj::Read(m) => &m[..copy_size], }; - // Safety: we just created new_mmap and know it's large enough - unsafe { - std::ptr::copy_nonoverlapping( - src.as_ptr(), - new_mmap.as_ptr() as *mut u8, - copy_size, - ); - } + new_mmap[..copy_size].copy_from_slice(src); } *mmap_guard = Some(MmapObj::Write(new_mmap)); @@ -1110,7 +1063,12 @@ mod mmap { // Restore original mmap on error let err = io::Error::last_os_error(); let old_size = self.size.load(); - if let Ok(mmap) = Self::create_mmap_windows(handle as HANDLE, self.offset, old_size, &self.access) { + if let Ok(mmap) = Self::create_mmap_windows( + handle as HANDLE, + self.offset, + old_size, + &self.access, + ) { *mmap_guard = Some(mmap); } return Err(err.to_pyexception(vm)); @@ -1120,15 +1078,21 @@ mod mmap { if result == 0 { let err = io::Error::last_os_error(); let old_size = self.size.load(); - if let Ok(mmap) = Self::create_mmap_windows(handle as HANDLE, self.offset, old_size, &self.access) { + if let Ok(mmap) = Self::create_mmap_windows( + handle as HANDLE, + self.offset, + old_size, + &self.access, + ) { *mmap_guard = Some(mmap); } return Err(err.to_pyexception(vm)); } // Create new mmap with the new size - let new_mmap = Self::create_mmap_windows(handle as HANDLE, self.offset, newsize, &self.access) - .map_err(|e| e.to_pyexception(vm))?; + let new_mmap = + Self::create_mmap_windows(handle as HANDLE, self.offset, newsize, &self.access) + .map_err(|e| e.to_pyexception(vm))?; *mmap_guard = Some(new_mmap); self.size.store(newsize); @@ -1305,16 +1269,16 @@ mod mmap { let result = match access { AccessMode::Default | AccessMode::Write => { - MmapObj::Write(unsafe { mmap_opt.map_mut(&file) }?) + unsafe { mmap_opt.map_mut(&file) }.map(MmapObj::Write) } - AccessMode::Read => MmapObj::Read(unsafe { mmap_opt.map(&file) }?), - AccessMode::Copy => MmapObj::Write(unsafe { mmap_opt.map_copy(&file) }?), + AccessMode::Read => unsafe { mmap_opt.map(&file) }.map(MmapObj::Read), + AccessMode::Copy => unsafe { mmap_opt.map_copy(&file) }.map(MmapObj::Write), }; // Don't close the file handle - we're borrowing it std::mem::forget(file); - Ok(result) + result } fn getitem_by_index(&self, i: isize, vm: &VirtualMachine) -> PyResult { @@ -1322,10 +1286,7 @@ mod mmap { .wrapped_at(self.__len__()) .ok_or_else(|| vm.new_index_error("mmap index out of range"))?; - let b = match self.check_valid(vm)?.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[i], - MmapObj::Write(mmap) => mmap[i], - }; + let b = self.check_valid(vm)?.deref().as_ref().unwrap().as_slice()[i]; Ok(PyInt::from(b).into_ref(&vm.ctx).into()) } @@ -1338,33 +1299,24 @@ mod mmap { let (range, step, slice_len) = slice.adjust_indices(self.__len__()); let mmap = self.check_valid(vm)?; + let slice_data = mmap.deref().as_ref().unwrap().as_slice(); if slice_len == 0 { return Ok(PyBytes::from(vec![]).into_ref(&vm.ctx).into()); } else if step == 1 { - let bytes = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => &mmap[range], - MmapObj::Write(mmap) => &mmap[range], - }; - return Ok(PyBytes::from(bytes.to_vec()).into_ref(&vm.ctx).into()); + return Ok(PyBytes::from(slice_data[range].to_vec()) + .into_ref(&vm.ctx) + .into()); } let mut result_buf = Vec::with_capacity(slice_len); if step.is_negative() { for i in range.rev().step_by(step.unsigned_abs()) { - let b = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[i], - MmapObj::Write(mmap) => mmap[i], - }; - result_buf.push(b); + result_buf.push(slice_data[i]); } } else { for i in range.step_by(step.unsigned_abs()) { - let b = match mmap.deref().as_ref().unwrap() { - MmapObj::Read(mmap) => mmap[i], - MmapObj::Write(mmap) => mmap[i], - }; - result_buf.push(b); + result_buf.push(slice_data[i]); } } Ok(PyBytes::from(result_buf).into_ref(&vm.ctx).into()) From c6086cb6d598a60c35af58f94b67898308cd2f4e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 5 Dec 2025 20:21:18 +0900 Subject: [PATCH 3/4] upgrade memmap2 --- Cargo.lock | 4 ++-- crates/stdlib/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aef688eb21f..569f52bc9bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1897,9 +1897,9 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 13c1eb2e60b..785e280f6ab 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -105,7 +105,7 @@ rustix = { workspace = true } # mmap + socket dependencies [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -memmap2 = "0.5.10" +memmap2 = "0.9.9" page_size = "0.6" gethostname = "1.0.2" socket2 = { version = "0.6.0", features = ["all"] } From 78e7f26ea3cf72e964c7dccc45b7b8470203fd8c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 6 Dec 2025 08:40:11 +0900 Subject: [PATCH 4/4] mmap for windows --- crates/stdlib/src/mmap.rs | 96 +++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index d21acc62941..7e53a27be85 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -53,7 +53,12 @@ mod mmap { | libc::MADV_SEQUENTIAL | libc::MADV_WILLNEED | libc::MADV_DONTNEED => Ok(advice), - #[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios"))] + #[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "freebsd" + ))] libc::MADV_FREE => Ok(advice), #[cfg(target_os = "linux")] libc::MADV_DONTFORK @@ -66,6 +71,12 @@ mod mmap { | libc::MADV_DONTDUMP | libc::MADV_DODUMP | libc::MADV_HWPOISON => Ok(advice), + #[cfg(target_os = "freebsd")] + libc::MADV_NOSYNC + | libc::MADV_AUTOSYNC + | libc::MADV_NOCORE + | libc::MADV_CORE + | libc::MADV_PROTECT => Ok(advice), _ => Err(vm.new_value_error("Not a valid Advice value")), } } @@ -96,7 +107,7 @@ mod mmap { #[pyattr] use libc::{ MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, MAP_ANON, - MAP_ANONYMOUS, MAP_PRIVATE, MAP_SHARED, PROT_READ, PROT_WRITE, + MAP_ANONYMOUS, MAP_PRIVATE, MAP_SHARED, PROT_EXEC, PROT_READ, PROT_WRITE, }; #[cfg(target_os = "macos")] @@ -146,6 +157,16 @@ mod mmap { #[pyattr] use libc::{MAP_DENYWRITE, MAP_EXECUTABLE, MAP_POPULATE}; + // MAP_STACK is available on Linux, OpenBSD, and NetBSD + #[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))] + #[pyattr] + use libc::MAP_STACK; + + // FreeBSD-specific MADV constants + #[cfg(target_os = "freebsd")] + #[pyattr] + use libc::{MADV_AUTOSYNC, MADV_CORE, MADV_NOCORE, MADV_NOSYNC, MADV_PROTECT}; + #[pyattr] const ACCESS_DEFAULT: u32 = AccessMode::Default as u32; #[pyattr] @@ -404,6 +425,15 @@ mod mmap { }; let fd = unsafe { crt_fd::Borrowed::try_borrow_raw(fd) }; + + // macOS: Issue #11277: fsync(2) is not enough on OS X - a special, OS X specific + // fcntl(2) is necessary to force DISKSYNC and get around mmap(2) bug + #[cfg(target_os = "macos")] + if let Ok(fd) = fd { + use std::os::fd::AsRawFd; + unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_FULLFSYNC) }; + } + if let Ok(fd) = fd { let metadata = fstat(fd) .map_err(|err| io::Error::from_raw_os_error(err as i32).to_pyexception(vm))?; @@ -538,7 +568,10 @@ mod mmap { map_size = (file_len - offset) as usize; } else { // If map_size > file_len, extend the file (Windows behavior) - let required_size = offset + map_size as i64; + let required_size = offset.checked_add(map_size as i64).ok_or_else(|| { + unsafe { CloseHandle(duplicated_handle) }; + vm.new_overflow_error("mmap size would cause file size overflow") + })?; if required_size > file_len { // Extend file using SetFilePointerEx + SetEndOfFile let result = unsafe { @@ -799,8 +832,9 @@ mod mmap { let sub = &options.sub; + // returns start position for empty string if sub.is_empty() { - return Ok(PyInt::from(0isize)); + return Ok(PyInt::from(start as isize)); } let mmap = self.check_valid(vm)?; @@ -815,8 +849,9 @@ mod mmap { let (start, end) = self.get_find_range(options.clone()); let sub = &options.sub; + // returns start position for empty string if sub.is_empty() { - return Ok(PyInt::from(0isize)); + return Ok(PyInt::from(start as isize)); } let mmap = self.check_valid(vm)?; @@ -850,18 +885,20 @@ mod mmap { #[cfg(all(unix, not(target_os = "redox")))] #[pymethod] fn madvise(&self, options: AdviseOptions, vm: &VirtualMachine) -> PyResult<()> { - let (option, _start, _length) = options.values(self.__len__(), vm)?; + let (option, start, length) = options.values(self.__len__(), vm)?; let advice = validate_advice(vm, option)?; let guard = self.check_valid(vm)?; let mmap = guard.deref().as_ref().unwrap(); - let (ptr, len) = match mmap { - MmapObj::Read(m) => (m.as_ptr(), m.len()), - MmapObj::Write(m) => (m.as_ptr(), m.len()), + let ptr = match mmap { + MmapObj::Read(m) => m.as_ptr(), + MmapObj::Write(m) => m.as_ptr(), }; - // Call libc::madvise directly - let result = unsafe { libc::madvise(ptr as *mut libc::c_void, len, advice) }; + // Apply madvise to the specified range (start, length) + let ptr_with_offset = unsafe { ptr.add(start) }; + let result = + unsafe { libc::madvise(ptr_with_offset as *mut libc::c_void, length, advice) }; if result != 0 { return Err(io::Error::last_os_error().to_pyexception(vm)); } @@ -1062,30 +1099,14 @@ mod mmap { if result == 0 { // Restore original mmap on error let err = io::Error::last_os_error(); - let old_size = self.size.load(); - if let Ok(mmap) = Self::create_mmap_windows( - handle as HANDLE, - self.offset, - old_size, - &self.access, - ) { - *mmap_guard = Some(mmap); - } + self.try_restore_mmap(&mut mmap_guard, handle as HANDLE, self.size.load()); return Err(err.to_pyexception(vm)); } let result = unsafe { SetEndOfFile(handle as HANDLE) }; if result == 0 { let err = io::Error::last_os_error(); - let old_size = self.size.load(); - if let Ok(mmap) = Self::create_mmap_windows( - handle as HANDLE, - self.offset, - old_size, - &self.access, - ) { - *mmap_guard = Some(mmap); - } + self.try_restore_mmap(&mut mmap_guard, handle as HANDLE, self.size.load()); return Err(err.to_pyexception(vm)); } @@ -1248,6 +1269,12 @@ mod mmap { fn __exit__(zelf: &Py, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { zelf.close(vm) } + + #[cfg(windows)] + #[pymethod] + fn __sizeof__(&self) -> usize { + std::mem::size_of::() + } } impl PyMmap { @@ -1281,6 +1308,17 @@ mod mmap { result } + /// Try to restore mmap after a failed resize operation. + /// Returns true if restoration succeeded, false otherwise. + /// If restoration fails, marks the mmap as closed. + #[cfg(windows)] + fn try_restore_mmap(&self, mmap_guard: &mut Option, handle: HANDLE, size: usize) { + match Self::create_mmap_windows(handle, self.offset, size, &self.access) { + Ok(mmap) => *mmap_guard = Some(mmap), + Err(_) => self.closed.store(true), + } + } + fn getitem_by_index(&self, i: isize, vm: &VirtualMachine) -> PyResult { let i = i .wrapped_at(self.__len__())