Problem
A newly created Windows event set, or one after its last registration is removed, passes a zero count to WSAPoll. The portable event API should return no events for a zero-timeout wait. Instead, this path can return a Winsock error even after Winsock initialization.
Microsoft's WSAPoll contract requires at least one valid socket and describes WSAEINVAL when none is valid. Starting points: std/sys/windows/event.wave, std/sys/windows/socket.wave, std/net/event.wave.
Reproduction
Audited at 4c3372157d8889fa8bbef0a7ccd2e72aab1c5c6c with LLVM 21.1.8. An executed Linux-hosted boundary mock retained the event provider and its poll/error-forwarding bodies, adapted only the two Winsock declarations to C ABI, and supplied allocation and OS-call mocks. At O0/O2:
new set: WSAPoll(count=0, timeout=0) -> event_wait returns -10022
one entry: WSAPoll(count=1, timeout=0) -> event_wait returns 0
entry removed: WSAPoll(count=0, timeout=0) -> event_wait returns -10022
The mock returns SOCKET_ERROR/WSAEINVAL for zero entries; this confirms forwarding, not native Windows execution. An empty Linux epoll control returned 0. The following native regression compiles to objects for both Windows amd64 and ARM64, but native execution remains to be done.
Prepare an alias of the matching checkout std from the repository root (this avoids using an older installed std):
Save this setup as prepare_std.py and run python prepare_std.py:
from pathlib import Path
out = Path(".tmp/additional-audit-std")
for source in Path("std").rglob("*.wave"):
dest = out / source.relative_to("std")
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(source.read_text().replace('"std::', '"checkout_std::'))
Save as empty_event.wave:
import("checkout_std::sys::event")::{NativeEvent, event_create, event_wait, event_close};
import("checkout_std::sys::windows::socket")::{socket_runtime_init};
fun main() -> i32 {
if (socket_runtime_init() < 0) { return 2; }
var handle: i64 = event_create(1);
if (handle < 0) { return 3; }
var output: NativeEvent;
var result: i64 = event_wait(handle, &output, 1, 0);
event_close(handle);
if (result == 0) { return 0; }
return 1;
}
On a Windows host with the checkout-built compiler and its linker prerequisites:
target/debug/wavec.exe build empty_event.wave --dep checkout_std=.tmp/additional-audit-std --run -O0 --out-dir .tmp/empty-event-run
Expected exit 0. Repeat at O2 and after removing the last registration.
Scope
Handle the empty-set wait before invoking Winsock. Preserve zero, finite and infinite timeout semantics without introducing a busy loop. Keep this synchronous readiness provider separate from IOCP. #530 covers result fairness for nonempty sets, not this invalid native call.
Difficulty: medium.
Completion criteria
Problem
A newly created Windows event set, or one after its last registration is removed, passes a zero count to
WSAPoll. The portable event API should return no events for a zero-timeout wait. Instead, this path can return a Winsock error even after Winsock initialization.Microsoft's WSAPoll contract requires at least one valid socket and describes WSAEINVAL when none is valid. Starting points: std/sys/windows/event.wave, std/sys/windows/socket.wave, std/net/event.wave.
Reproduction
Audited at
4c3372157d8889fa8bbef0a7ccd2e72aab1c5c6cwith LLVM 21.1.8. An executed Linux-hosted boundary mock retained the event provider and itspoll/error-forwarding bodies, adapted only the two Winsock declarations to C ABI, and supplied allocation and OS-call mocks. At O0/O2:The mock returns SOCKET_ERROR/WSAEINVAL for zero entries; this confirms forwarding, not native Windows execution. An empty Linux epoll control returned 0. The following native regression compiles to objects for both Windows amd64 and ARM64, but native execution remains to be done.
Prepare an alias of the matching checkout std from the repository root (this avoids using an older installed std):
Save this setup as
prepare_std.pyand runpython prepare_std.py:Save as
empty_event.wave:On a Windows host with the checkout-built compiler and its linker prerequisites:
Expected exit 0. Repeat at O2 and after removing the last registration.
Scope
Handle the empty-set wait before invoking Winsock. Preserve zero, finite and infinite timeout semantics without introducing a busy loop. Keep this synchronous readiness provider separate from IOCP. #530 covers result fairness for nonempty sets, not this invalid native call.
Difficulty: medium.
Completion criteria