Skip to content

Commit fe984ce

Browse files
authored
select: validate and expand Windows descriptor capacity (#9021)
* Cover Windows select capacity above the SDK default Add local-socket readiness coverage for 65 entries and the Windows 512-entry limit, preserving 513-entry rejection. This regression-only commit intentionally leaves the current Windows 64-entry defect visible before the implementation is added. AI assistance covered regression design, CPython 3.14 source comparison, and automated Linux baseline/reference testing. No manual Windows validation is claimed. Assisted-by: Codex:model-version-unavailable * Match Windows select capacity to Python 3.14 Use a 512-entry WinSock fd_set with checked C layout and scan its returned socket array. Keep the SDK default-size declaration at the FFI boundary only. The preceding regression-only commit passed CPython 3.14.7 on hosted Windows and failed RustPython at the first 65-socket selection. Windows-GNU host_env cargo check and Clippy with warnings denied pass. The fixed native Windows runtime result remains pending CI. AI assistance covered source comparison, implementation, regression design, and automated verification. No manual validation is claimed. Assisted-by: Codex:model-version-unavailable
1 parent bd8fdb7 commit fe984ce

2 files changed

Lines changed: 74 additions & 3 deletions

File tree

‎crates/host_env/src/select.rs‎

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,62 @@ pub mod platform {
3636
#[allow(non_snake_case)]
3737
#[cfg(windows)]
3838
pub mod platform {
39-
pub use WinSock::{FD_SET as fd_set, FD_SETSIZE, SOCKET as RawFd, TIMEVAL as timeval, select};
39+
pub use WinSock::{SOCKET as RawFd, TIMEVAL as timeval};
4040
use std::io;
4141
use windows_sys::Win32::Networking::WinSock;
4242

43+
// Match CPython's Windows capacity. WinSock uses fd_count to determine
44+
// the array length; the windows-sys declaration has the SDK default of 64.
45+
pub const FD_SETSIZE: u32 = 512;
46+
47+
#[allow(non_camel_case_types)]
48+
#[repr(C)]
49+
pub struct fd_set {
50+
fd_count: u32,
51+
fd_array: [RawFd; FD_SETSIZE as usize],
52+
}
53+
54+
const _: () = {
55+
assert!(core::mem::align_of::<fd_set>() == core::mem::align_of::<WinSock::FD_SET>());
56+
assert!(
57+
core::mem::offset_of!(fd_set, fd_count)
58+
== core::mem::offset_of!(WinSock::FD_SET, fd_count)
59+
);
60+
assert!(
61+
core::mem::offset_of!(fd_set, fd_array)
62+
== core::mem::offset_of!(WinSock::FD_SET, fd_array)
63+
);
64+
assert!(
65+
core::mem::size_of::<fd_set>()
66+
== core::mem::offset_of!(fd_set, fd_array)
67+
+ FD_SETSIZE as usize * core::mem::size_of::<RawFd>()
68+
);
69+
};
70+
71+
/// # Safety
72+
///
73+
/// Every non-null set must point to an initialized fd_set, and timeout
74+
/// must be null or point to an initialized timeval.
75+
pub unsafe fn select(
76+
nfds: i32,
77+
readfds: *mut fd_set,
78+
writefds: *mut fd_set,
79+
errorfds: *mut fd_set,
80+
timeout: *const timeval,
81+
) -> i32 {
82+
// fd_set is a count followed by a caller-sized SOCKET array. Both
83+
// layouts have the same alignment and offset of the array.
84+
unsafe {
85+
WinSock::select(
86+
nfds,
87+
readfds.cast(),
88+
writefds.cast(),
89+
errorfds.cast(),
90+
timeout,
91+
)
92+
}
93+
}
94+
4395
/// # Safety
4496
///
4597
/// `set` must be a valid mutable pointer to an initialized WinSock fd_set.
@@ -71,8 +123,8 @@ pub mod platform {
71123
///
72124
/// `set` must be a valid mutable pointer to an initialized WinSock fd_set.
73125
pub unsafe fn FD_ISSET(fd: RawFd, set: *mut fd_set) -> bool {
74-
use WinSock::__WSAFDIsSet;
75-
unsafe { __WSAFDIsSet(fd as _, set) != 0 }
126+
let set = unsafe { &*set };
127+
set.fd_array[..set.fd_count as usize].contains(&fd)
76128
}
77129

78130
#[must_use]

‎extra_tests/snippets/stdlib_select.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,25 @@ def fileno(self):
8383
b.close()
8484

8585

86+
# Windows supports up to 512 entries, above the Windows SDK default of 64.
87+
for count in (65, 512) if sys.platform == "win32" else (65,):
88+
pairs = []
89+
try:
90+
for _ in range(count):
91+
pairs.append(socket.socketpair())
92+
readers = [reader for reader, writer in pairs]
93+
assert select.select(readers, [], [], 0) == ([], [], [])
94+
for reader, writer in pairs:
95+
writer.send(b"x")
96+
assert select.select(readers, [], [], 0) == (readers, [], [])
97+
if sys.platform == "win32":
98+
assert_raises(ValueError, select.select, [readers[0]] * 513, [], [], 0)
99+
finally:
100+
for pair in pairs:
101+
for sock in pair:
102+
sock.close()
103+
104+
86105
# fileno() runs while the sequence is being read, and it can mutate the very
87106
# list it was handed.
88107
mutable_pair, other_end = socket.socketpair()

0 commit comments

Comments
 (0)