mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 11:14:38 +08:00
Implement Windows named mmap via CreateFileMappingW (#7207)
Use Win32 CreateFileMappingW + MapViewOfFile when tagname is provided, enabling inter-process shared memory for multiprocessing.heap.Arena. Anonymous mmaps without tagname still use memmap2. Remove expectedFailure from test_tagname in test_mmap.py.
This commit is contained in:
1 parent
25437b99d5
commit
cb7f19c316
3 files changed
+163
-10
No files matched your search
Vendored
-1
@@ -618,7 +618,6 @@ class MmapTests(unittest.TestCase):
|
||||
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"
|
||||
|
||||
@@ -160,6 +160,7 @@ features = [
|
||||
"Win32_System_Environment",
|
||||
"Win32_System_Console",
|
||||
"Win32_System_IO",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Threading"
|
||||
]
|
||||
|
||||
|
||||
+162
-9
@@ -42,6 +42,10 @@ mod mmap {
|
||||
CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE,
|
||||
},
|
||||
Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx},
|
||||
System::Memory::{
|
||||
CreateFileMappingW, FILE_MAP_COPY, FILE_MAP_READ, FILE_MAP_WRITE, FlushViewOfFile,
|
||||
MapViewOfFile, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY, UnmapViewOfFile,
|
||||
},
|
||||
System::Threading::GetCurrentProcess,
|
||||
};
|
||||
|
||||
@@ -193,10 +197,57 @@ mod mmap {
|
||||
vm.ctx.exceptions.os_error.to_owned()
|
||||
}
|
||||
|
||||
/// Named file mapping on Windows using raw Win32 APIs.
|
||||
/// Supports tagname parameter for inter-process shared memory.
|
||||
#[cfg(windows)]
|
||||
struct NamedMmap {
|
||||
map_handle: HANDLE,
|
||||
view_ptr: *mut u8,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
// SAFETY: The memory mapping is managed by the OS and is safe to share
|
||||
// across threads. Access is synchronized by PyMutex in PyMmap.
|
||||
unsafe impl Send for NamedMmap {}
|
||||
#[cfg(windows)]
|
||||
unsafe impl Sync for NamedMmap {}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl core::fmt::Debug for NamedMmap {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("NamedMmap")
|
||||
.field("map_handle", &self.map_handle)
|
||||
.field("view_ptr", &self.view_ptr)
|
||||
.field("len", &self.len)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Drop for NamedMmap {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.view_ptr.is_null() {
|
||||
UnmapViewOfFile(
|
||||
windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS {
|
||||
Value: self.view_ptr as *mut _,
|
||||
},
|
||||
);
|
||||
}
|
||||
if !self.map_handle.is_null() {
|
||||
CloseHandle(self.map_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MmapObj {
|
||||
Write(MmapMut),
|
||||
Read(Mmap),
|
||||
#[cfg(windows)]
|
||||
Named(NamedMmap),
|
||||
}
|
||||
|
||||
impl MmapObj {
|
||||
@@ -204,6 +255,10 @@ mod mmap {
|
||||
match self {
|
||||
MmapObj::Read(mmap) => &mmap[..],
|
||||
MmapObj::Write(mmap) => &mmap[..],
|
||||
#[cfg(windows)]
|
||||
MmapObj::Named(named) => unsafe {
|
||||
core::slice::from_raw_parts(named.view_ptr, named.len)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,7 +331,6 @@ mod mmap {
|
||||
#[pyarg(any)]
|
||||
length: isize,
|
||||
#[pyarg(any, default)]
|
||||
#[allow(dead_code)]
|
||||
tagname: Option<PyObjectRef>,
|
||||
#[pyarg(any, default = AccessMode::Default)]
|
||||
access: AccessMode,
|
||||
@@ -494,11 +548,22 @@ mod mmap {
|
||||
let mut map_size = args.validate_new_args(vm)?;
|
||||
let MmapNewArgs {
|
||||
fileno,
|
||||
tagname,
|
||||
access,
|
||||
offset,
|
||||
..
|
||||
} = args;
|
||||
|
||||
// Parse tagname: None or a string
|
||||
let tag_str: Option<String> = match tagname {
|
||||
Some(ref obj) if !vm.is_none(obj) => {
|
||||
Some(obj.try_to_value::<String>(vm).map_err(|_| {
|
||||
vm.new_type_error("tagname must be a string or None".to_owned())
|
||||
})?)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Get file handle from fileno
|
||||
// fileno -1 or 0 means anonymous mapping
|
||||
let fh: Option<HANDLE> = if fileno != -1 && fileno != 0 {
|
||||
@@ -595,6 +660,75 @@ mod mmap {
|
||||
}
|
||||
}
|
||||
|
||||
// When tagname is provided, use raw Win32 APIs for named shared memory
|
||||
if let Some(ref tag) = tag_str {
|
||||
let (fl_protect, desired_access) = match access {
|
||||
AccessMode::Default | AccessMode::Write => (PAGE_READWRITE, FILE_MAP_WRITE),
|
||||
AccessMode::Read => (PAGE_READONLY, FILE_MAP_READ),
|
||||
AccessMode::Copy => (PAGE_WRITECOPY, FILE_MAP_COPY),
|
||||
};
|
||||
|
||||
let fh = if let Some(fh) = fh {
|
||||
// Close the duplicated handle - we'll use the original
|
||||
// file handle for CreateFileMappingW
|
||||
if duplicated_handle != INVALID_HANDLE_VALUE {
|
||||
unsafe { CloseHandle(duplicated_handle) };
|
||||
}
|
||||
fh
|
||||
} else {
|
||||
INVALID_HANDLE_VALUE
|
||||
};
|
||||
|
||||
let tag_wide: Vec<u16> = tag.encode_utf16().chain(core::iter::once(0)).collect();
|
||||
|
||||
let total_size = (offset as u64)
|
||||
.checked_add(map_size as u64)
|
||||
.ok_or_else(|| vm.new_overflow_error("mmap offset plus size would overflow"))?;
|
||||
let size_hi = (total_size >> 32) as u32;
|
||||
let size_lo = total_size as u32;
|
||||
|
||||
let map_handle = unsafe {
|
||||
CreateFileMappingW(
|
||||
fh,
|
||||
core::ptr::null(),
|
||||
fl_protect,
|
||||
size_hi,
|
||||
size_lo,
|
||||
tag_wide.as_ptr(),
|
||||
)
|
||||
};
|
||||
if map_handle.is_null() {
|
||||
return Err(io::Error::last_os_error().to_pyexception(vm));
|
||||
}
|
||||
|
||||
let off_hi = (offset as u64 >> 32) as u32;
|
||||
let off_lo = offset as u32;
|
||||
|
||||
let view =
|
||||
unsafe { MapViewOfFile(map_handle, desired_access, off_hi, off_lo, map_size) };
|
||||
if view.Value.is_null() {
|
||||
unsafe { CloseHandle(map_handle) };
|
||||
return Err(io::Error::last_os_error().to_pyexception(vm));
|
||||
}
|
||||
|
||||
let named = NamedMmap {
|
||||
map_handle,
|
||||
view_ptr: view.Value as *mut u8,
|
||||
len: map_size,
|
||||
};
|
||||
|
||||
return Ok(Self {
|
||||
closed: AtomicCell::new(false),
|
||||
mmap: PyMutex::new(Some(MmapObj::Named(named))),
|
||||
handle: AtomicCell::new(INVALID_HANDLE_VALUE as isize),
|
||||
offset,
|
||||
size: AtomicCell::new(map_size),
|
||||
pos: AtomicCell::new(0),
|
||||
exports: AtomicCell::new(0),
|
||||
access,
|
||||
});
|
||||
}
|
||||
|
||||
let mut mmap_opt = MmapOptions::new();
|
||||
let mmap_opt = mmap_opt.offset(offset as u64).len(map_size);
|
||||
|
||||
@@ -719,6 +853,10 @@ mod mmap {
|
||||
match m.as_mut().expect("mmap closed or invalid") {
|
||||
MmapObj::Read(_) => panic!("mmap can't modify a readonly memory map."),
|
||||
MmapObj::Write(mmap) => &mut mmap[..],
|
||||
#[cfg(windows)]
|
||||
MmapObj::Named(named) => unsafe {
|
||||
core::slice::from_raw_parts_mut(named.view_ptr, named.len)
|
||||
},
|
||||
}
|
||||
})
|
||||
.into()
|
||||
@@ -749,15 +887,19 @@ mod mmap {
|
||||
fn try_writable<R>(
|
||||
&self,
|
||||
vm: &VirtualMachine,
|
||||
f: impl FnOnce(&mut MmapMut) -> R,
|
||||
f: impl FnOnce(&mut [u8]) -> R,
|
||||
) -> PyResult<R> {
|
||||
if matches!(self.access, AccessMode::Read) {
|
||||
return Err(vm.new_type_error("mmap can't modify a readonly memory map."));
|
||||
}
|
||||
|
||||
match self.check_valid(vm)?.deref_mut().as_mut().unwrap() {
|
||||
MmapObj::Write(mmap) => Ok(f(mmap)),
|
||||
_ => unreachable!("already check"),
|
||||
MmapObj::Write(mmap) => Ok(f(&mut mmap[..])),
|
||||
#[cfg(windows)]
|
||||
MmapObj::Named(named) => Ok(f(unsafe {
|
||||
core::slice::from_raw_parts_mut(named.view_ptr, named.len)
|
||||
})),
|
||||
_ => unreachable!("already checked"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,6 +1015,14 @@ mod mmap {
|
||||
mmap.flush_range(offset, size)
|
||||
.map_err(|e| e.to_pyexception(vm))?;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
MmapObj::Named(named) => {
|
||||
let ptr = unsafe { named.view_ptr.add(offset) };
|
||||
let result = unsafe { FlushViewOfFile(ptr as *const _, size) };
|
||||
if result == 0 {
|
||||
return Err(io::Error::last_os_error().to_pyexception(vm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1044,11 +1194,17 @@ mod mmap {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Check if this is a Named mmap - these cannot be resized
|
||||
if let Some(MmapObj::Named(_)) = mmap_guard.as_ref() {
|
||||
return Err(vm.new_system_error("mmap: cannot resize a named memory mapping"));
|
||||
}
|
||||
|
||||
let is_anonymous = handle == INVALID_HANDLE_VALUE as isize;
|
||||
|
||||
if is_anonymous {
|
||||
// For anonymous mmap, we need to:
|
||||
// 1. Create a new anonymous mmap with the new size
|
||||
@@ -1067,10 +1223,7 @@ mod mmap {
|
||||
|
||||
// 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],
|
||||
};
|
||||
let src = &old_mmap.as_slice()[..copy_size];
|
||||
new_mmap[..copy_size].copy_from_slice(src);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user