Skip to content

Commit 399f67f

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 39a5d39 commit 399f67f

File tree

3 files changed

+163
-10
lines changed

3 files changed

+163
-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: 162 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ mod mmap {
4242
CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE,
4343
},
4444
Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx},
45+
System::Memory::{
46+
CreateFileMappingW, FILE_MAP_COPY, FILE_MAP_READ, FILE_MAP_WRITE, FlushViewOfFile,
47+
MapViewOfFile, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY, UnmapViewOfFile,
48+
},
4549
System::Threading::GetCurrentProcess,
4650
};
4751

@@ -193,17 +197,68 @@ mod mmap {
193197
vm.ctx.exceptions.os_error.to_owned()
194198
}
195199

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

202253
impl MmapObj {
203254
fn as_slice(&self) -> &[u8] {
204255
match self {
205256
MmapObj::Read(mmap) => &mmap[..],
206257
MmapObj::Write(mmap) => &mmap[..],
258+
#[cfg(windows)]
259+
MmapObj::Named(named) => unsafe {
260+
core::slice::from_raw_parts(named.view_ptr, named.len)
261+
},
207262
}
208263
}
209264
}
@@ -276,7 +331,6 @@ mod mmap {
276331
#[pyarg(any)]
277332
length: isize,
278333
#[pyarg(any, default)]
279-
#[allow(dead_code)]
280334
tagname: Option<PyObjectRef>,
281335
#[pyarg(any, default = AccessMode::Default)]
282336
access: AccessMode,
@@ -494,11 +548,22 @@ mod mmap {
494548
let mut map_size = args.validate_new_args(vm)?;
495549
let MmapNewArgs {
496550
fileno,
551+
tagname,
497552
access,
498553
offset,
499554
..
500555
} = args;
501556

557+
// Parse tagname: None or a string
558+
let tag_str: Option<String> = match tagname {
559+
Some(ref obj) if !vm.is_none(obj) => {
560+
Some(obj.try_to_value::<String>(vm).map_err(|_| {
561+
vm.new_type_error("tagname must be a string or None".to_owned())
562+
})?)
563+
}
564+
_ => None,
565+
};
566+
502567
// Get file handle from fileno
503568
// fileno -1 or 0 means anonymous mapping
504569
let fh: Option<HANDLE> = if fileno != -1 && fileno != 0 {
@@ -595,6 +660,75 @@ mod mmap {
595660
}
596661
}
597662

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

@@ -719,6 +853,10 @@ mod mmap {
719853
match m.as_mut().expect("mmap closed or invalid") {
720854
MmapObj::Read(_) => panic!("mmap can't modify a readonly memory map."),
721855
MmapObj::Write(mmap) => &mut mmap[..],
856+
#[cfg(windows)]
857+
MmapObj::Named(named) => unsafe {
858+
core::slice::from_raw_parts_mut(named.view_ptr, named.len)
859+
},
722860
}
723861
})
724862
.into()
@@ -749,15 +887,19 @@ mod mmap {
749887
fn try_writable<R>(
750888
&self,
751889
vm: &VirtualMachine,
752-
f: impl FnOnce(&mut MmapMut) -> R,
890+
f: impl FnOnce(&mut [u8]) -> R,
753891
) -> PyResult<R> {
754892
if matches!(self.access, AccessMode::Read) {
755893
return Err(vm.new_type_error("mmap can't modify a readonly memory map."));
756894
}
757895

758896
match self.check_valid(vm)?.deref_mut().as_mut().unwrap() {
759-
MmapObj::Write(mmap) => Ok(f(mmap)),
760-
_ => unreachable!("already check"),
897+
MmapObj::Write(mmap) => Ok(f(&mut mmap[..])),
898+
#[cfg(windows)]
899+
MmapObj::Named(named) => Ok(f(unsafe {
900+
core::slice::from_raw_parts_mut(named.view_ptr, named.len)
901+
})),
902+
_ => unreachable!("already checked"),
761903
}
762904
}
763905

@@ -873,6 +1015,14 @@ mod mmap {
8731015
mmap.flush_range(offset, size)
8741016
.map_err(|e| e.to_pyexception(vm))?;
8751017
}
1018+
#[cfg(windows)]
1019+
MmapObj::Named(named) => {
1020+
let ptr = unsafe { named.view_ptr.add(offset) };
1021+
let result = unsafe { FlushViewOfFile(ptr as *const _, size) };
1022+
if result == 0 {
1023+
return Err(io::Error::last_os_error().to_pyexception(vm));
1024+
}
1025+
}
8761026
}
8771027

8781028
Ok(())
@@ -1044,11 +1194,17 @@ mod mmap {
10441194
}
10451195

10461196
let handle = self.handle.load();
1047-
let is_anonymous = handle == INVALID_HANDLE_VALUE as isize;
10481197

10491198
// Get the lock on mmap
10501199
let mut mmap_guard = self.mmap.lock();
10511200

1201+
// Check if this is a Named mmap - these cannot be resized
1202+
if let Some(MmapObj::Named(_)) = mmap_guard.as_ref() {
1203+
return Err(vm.new_system_error("mmap: cannot resize a named memory mapping"));
1204+
}
1205+
1206+
let is_anonymous = handle == INVALID_HANDLE_VALUE as isize;
1207+
10521208
if is_anonymous {
10531209
// For anonymous mmap, we need to:
10541210
// 1. Create a new anonymous mmap with the new size
@@ -1067,10 +1223,7 @@ mod mmap {
10671223

10681224
// Copy data from old mmap to new mmap
10691225
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-
};
1226+
let src = &old_mmap.as_slice()[..copy_size];
10741227
new_mmap[..copy_size].copy_from_slice(src);
10751228
}
10761229

0 commit comments

Comments
 (0)