Skip to content

Commit 6ea7862

Browse files
committed
Implement Windows named mmap via CreateFileMappingW
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.
1 parent f096bb7 commit 6ea7862

File tree

3 files changed

+150
-10
lines changed

3 files changed

+150
-10
lines changed

Lib/test/test_mmap.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,6 @@ def test_non_ascii_byte(self):
618618
self.assertEqual(m.read_byte(), b)
619619
m.close()
620620

621-
@unittest.expectedFailure # TODO: RUSTPYTHON
622621
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
623622
def test_tagname(self):
624623
data1 = b"0123456789"

crates/stdlib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ features = [
160160
"Win32_System_Environment",
161161
"Win32_System_Console",
162162
"Win32_System_IO",
163+
"Win32_System_Memory",
163164
"Win32_System_Threading"
164165
]
165166

crates/stdlib/src/mmap.rs

Lines changed: 149 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,14 @@ mod mmap {
3939
#[cfg(windows)]
4040
use windows_sys::Win32::{
4141
Foundation::{
42-
CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE,
42+
CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE,
43+
INVALID_HANDLE_VALUE,
4344
},
4445
Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx},
46+
System::Memory::{
47+
CreateFileMappingW, FILE_MAP_COPY, FILE_MAP_READ, FILE_MAP_WRITE, MapViewOfFile,
48+
PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY, UnmapViewOfFile,
49+
},
4550
System::Threading::GetCurrentProcess,
4651
};
4752

@@ -193,17 +198,66 @@ mod mmap {
193198
vm.ctx.exceptions.os_error.to_owned()
194199
}
195200

201+
/// Named file mapping on Windows using raw Win32 APIs.
202+
/// Supports tagname parameter for inter-process shared memory.
203+
#[cfg(windows)]
204+
struct NamedMmap {
205+
map_handle: HANDLE,
206+
view_ptr: *mut u8,
207+
len: usize,
208+
}
209+
210+
#[cfg(windows)]
211+
// SAFETY: The memory mapping is managed by the OS and is safe to share
212+
// across threads. Access is synchronized by PyMutex in PyMmap.
213+
unsafe impl Send for NamedMmap {}
214+
#[cfg(windows)]
215+
unsafe impl Sync for NamedMmap {}
216+
217+
#[cfg(windows)]
218+
impl core::fmt::Debug for NamedMmap {
219+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220+
f.debug_struct("NamedMmap")
221+
.field("map_handle", &self.map_handle)
222+
.field("view_ptr", &self.view_ptr)
223+
.field("len", &self.len)
224+
.finish()
225+
}
226+
}
227+
228+
#[cfg(windows)]
229+
impl Drop for NamedMmap {
230+
fn drop(&mut self) {
231+
unsafe {
232+
if !self.view_ptr.is_null() {
233+
UnmapViewOfFile(windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS {
234+
Value: self.view_ptr as *mut _,
235+
});
236+
}
237+
if !self.map_handle.is_null() {
238+
CloseHandle(self.map_handle);
239+
}
240+
}
241+
}
242+
}
243+
196244
#[derive(Debug)]
197245
enum MmapObj {
198246
Write(MmapMut),
199247
Read(Mmap),
248+
#[cfg(windows)]
249+
Named(NamedMmap),
200250
}
201251

202252
impl MmapObj {
203253
fn as_slice(&self) -> &[u8] {
204254
match self {
205255
MmapObj::Read(mmap) => &mmap[..],
206256
MmapObj::Write(mmap) => &mmap[..],
257+
#[cfg(windows)]
258+
MmapObj::Named(named) => unsafe {
259+
core::slice::from_raw_parts(named.view_ptr, named.len)
260+
},
207261
}
208262
}
209263
}
@@ -276,7 +330,6 @@ mod mmap {
276330
#[pyarg(any)]
277331
length: isize,
278332
#[pyarg(any, default)]
279-
#[allow(dead_code)]
280333
tagname: Option<PyObjectRef>,
281334
#[pyarg(any, default = AccessMode::Default)]
282335
access: AccessMode,
@@ -494,11 +547,22 @@ mod mmap {
494547
let mut map_size = args.validate_new_args(vm)?;
495548
let MmapNewArgs {
496549
fileno,
550+
tagname,
497551
access,
498552
offset,
499553
..
500554
} = args;
501555

556+
// Parse tagname: None or a string
557+
let tag_str: Option<String> = match tagname {
558+
Some(ref obj) if !vm.is_none(obj) => {
559+
Some(obj.try_to_value::<String>(vm).map_err(|_| {
560+
vm.new_type_error("tagname must be a string or None".to_owned())
561+
})?)
562+
}
563+
_ => None,
564+
};
565+
502566
// Get file handle from fileno
503567
// fileno -1 or 0 means anonymous mapping
504568
let fh: Option<HANDLE> = if fileno != -1 && fileno != 0 {
@@ -595,6 +659,73 @@ mod mmap {
595659
}
596660
}
597661

662+
// When tagname is provided, use raw Win32 APIs for named shared memory
663+
if let Some(ref tag) = tag_str {
664+
let (fl_protect, desired_access) = match access {
665+
AccessMode::Default | AccessMode::Write => (PAGE_READWRITE, FILE_MAP_WRITE),
666+
AccessMode::Read => (PAGE_READONLY, FILE_MAP_READ),
667+
AccessMode::Copy => (PAGE_WRITECOPY, FILE_MAP_COPY),
668+
};
669+
670+
let fh = if let Some(fh) = fh {
671+
// Close the duplicated handle - we'll use the original
672+
// file handle for CreateFileMappingW
673+
if duplicated_handle != INVALID_HANDLE_VALUE {
674+
unsafe { CloseHandle(duplicated_handle) };
675+
}
676+
fh
677+
} else {
678+
INVALID_HANDLE_VALUE
679+
};
680+
681+
let tag_wide: Vec<u16> = tag.encode_utf16().chain(core::iter::once(0)).collect();
682+
683+
let total_size = (offset as u64) + (map_size as u64);
684+
let size_hi = (total_size >> 32) as u32;
685+
let size_lo = total_size as u32;
686+
687+
let map_handle = unsafe {
688+
CreateFileMappingW(
689+
fh,
690+
core::ptr::null(),
691+
fl_protect,
692+
size_hi,
693+
size_lo,
694+
tag_wide.as_ptr(),
695+
)
696+
};
697+
if map_handle.is_null() {
698+
return Err(io::Error::last_os_error().to_pyexception(vm));
699+
}
700+
701+
let off_hi = (offset as u64 >> 32) as u32;
702+
let off_lo = offset as u32;
703+
704+
let view =
705+
unsafe { MapViewOfFile(map_handle, desired_access, off_hi, off_lo, map_size) };
706+
if view.Value.is_null() {
707+
unsafe { CloseHandle(map_handle) };
708+
return Err(io::Error::last_os_error().to_pyexception(vm));
709+
}
710+
711+
let named = NamedMmap {
712+
map_handle,
713+
view_ptr: view.Value as *mut u8,
714+
len: map_size,
715+
};
716+
717+
return Ok(Self {
718+
closed: AtomicCell::new(false),
719+
mmap: PyMutex::new(Some(MmapObj::Named(named))),
720+
handle: AtomicCell::new(INVALID_HANDLE_VALUE as isize),
721+
offset,
722+
size: AtomicCell::new(map_size),
723+
pos: AtomicCell::new(0),
724+
exports: AtomicCell::new(0),
725+
access,
726+
});
727+
}
728+
598729
let mut mmap_opt = MmapOptions::new();
599730
let mmap_opt = mmap_opt.offset(offset as u64).len(map_size);
600731

@@ -719,6 +850,10 @@ mod mmap {
719850
match m.as_mut().expect("mmap closed or invalid") {
720851
MmapObj::Read(_) => panic!("mmap can't modify a readonly memory map."),
721852
MmapObj::Write(mmap) => &mut mmap[..],
853+
#[cfg(windows)]
854+
MmapObj::Named(named) => unsafe {
855+
core::slice::from_raw_parts_mut(named.view_ptr, named.len)
856+
},
722857
}
723858
})
724859
.into()
@@ -749,15 +884,19 @@ mod mmap {
749884
fn try_writable<R>(
750885
&self,
751886
vm: &VirtualMachine,
752-
f: impl FnOnce(&mut MmapMut) -> R,
887+
f: impl FnOnce(&mut [u8]) -> R,
753888
) -> PyResult<R> {
754889
if matches!(self.access, AccessMode::Read) {
755890
return Err(vm.new_type_error("mmap can't modify a readonly memory map."));
756891
}
757892

758893
match self.check_valid(vm)?.deref_mut().as_mut().unwrap() {
759-
MmapObj::Write(mmap) => Ok(f(mmap)),
760-
_ => unreachable!("already check"),
894+
MmapObj::Write(mmap) => Ok(f(&mut mmap[..])),
895+
#[cfg(windows)]
896+
MmapObj::Named(named) => Ok(f(unsafe {
897+
core::slice::from_raw_parts_mut(named.view_ptr, named.len)
898+
})),
899+
_ => unreachable!("already checked"),
761900
}
762901
}
763902

@@ -873,6 +1012,10 @@ mod mmap {
8731012
mmap.flush_range(offset, size)
8741013
.map_err(|e| e.to_pyexception(vm))?;
8751014
}
1015+
#[cfg(windows)]
1016+
MmapObj::Named(_) => {
1017+
// Named mmaps backed by page file don't need explicit flushing
1018+
}
8761019
}
8771020

8781021
Ok(())
@@ -1067,10 +1210,7 @@ mod mmap {
10671210

10681211
// Copy data from old mmap to new mmap
10691212
if let Some(old_mmap) = mmap_guard.as_ref() {
1070-
let src = match old_mmap {
1071-
MmapObj::Write(m) => &m[..copy_size],
1072-
MmapObj::Read(m) => &m[..copy_size],
1073-
};
1213+
let src = &old_mmap.as_slice()[..copy_size];
10741214
new_mmap[..copy_size].copy_from_slice(src);
10751215
}
10761216

0 commit comments

Comments
 (0)