Skip to content

Windows: build and run natively - #33

Merged
endel merged 1 commit into
mainfrom
windows-support
Oct 30, 2026
Merged

endel merged 1 commit into
mainfrom
windows-support

Conversation

@endel

@endel endel commented Sep 14, 2026 •

Copy link
Copy Markdown
Owner

Summary

quic-zig builds, tests and runs natively on Windows again, and CI gets back its windows-latest job. Rebased onto main (Zig 0.17), which added the HTTP/1.1 and WebSocket stack after this PR was first opened, so that is ported too.

  • Syscall seam. Windows lives in src/sys/windows.zig: Winsock for sockets, std.Io for files and clocks. The per-function OS switches in src/sys.zig became one supported-OS check.
  • Helpers instead of repeated POSIX setup. sys.udpSocket, tcpSocket, setsockopt, setReuseAddr, setReusePort, setDualStack, send, sendVec, shutdown and argsIterator replace socket setup and raw std.c calls across src/ and the apps.
  • Hosting the loop on Windows. xev_backend.PortWatch stands in for pollFd where a completion port has no fd to poll: a thread waits on the port, puts each packet back, and wakes the host.
  • libxev. Pinned to quic-zig-2026-10-30, which carries mitchellh/libxev#240 (readiness through AFD), #259 (run(.no_wait) never asked the completion port) and #260 (a shut-down socket panicked in safe builds).

Also a fix on every platform: onReadable on Server and Client rearmed a cancelled watch, so on a backend that hands a cancelled watch its error they never finished stopping on a shared loop.

Windows behaviour worth knowing

In SPEC/STATUS.md#platforms:

  • ECN is neither read nor set, and there is no send batching or UDP offload.
  • SO_REUSEADDR isn't set, and a UDP socket no longer fails its next receive after an ICMP port-unreachable.
  • No Unix sockets or SO_REUSEPORT: Http1Config.unix_path and reuse_port return an error.
  • An HTTP/1.1 connection can't move between loops, as a socket stays on its first completion port.
  • A nonblocking send can take all of a large write, so backpressure shows a write later.
  • Timed waits round up to the ~15.6 ms system tick. sys.sleepNs under a millisecond yields in a loop instead.

Testing

  • Windows (aarch64, under Wine 10): zig build test 1308 passed, 20 skipped; zig build fuzz 908 passed, 3 skipped. This PR's windows-latest job is the first run on real Windows.
  • Linux (aarch64): zig build test 1328/1328, zig build fuzz 911/911.
  • macOS (aarch64): zig build test 1323 passed, 5 skipped; zig build fuzz 909 passed, 2 skipped.
  • routez tests/e2e/run.sh on this pin: macOS 475 passed, Linux 482 passed.
  • netcode-io on this branch, both thread modes: 33/33 on Linux and macOS, and 29 passed, 4 skipped on Windows under Wine.

The skipped Windows tests need a feature above, or POSIX behaviour such as dup.

🤖 Generated with Claude Code

sys.zig was POSIX-only, with a compile error on every Windows branch. Windows
now lives in sys/windows.zig: Winsock for sockets, std.Io for files, clocks,
randomness and sleep. Callers go through new helpers instead of spelling out
POSIX socket setup — udpSocket, setsockopt, setReuseAddr, stdout and
argsIterator — which replaced setup code repeated across src and 26 apps.

The event loop waits for readiness, which libxev's IOCP backend lacked, so
build.zig.zon pins a libxev fork that adds poll through the AFD driver.

On Windows a UDP socket no longer fails its next receive after an ICMP
port-unreachable, SO_REUSEADDR is not set (it lets another socket take a port
in use there), and ECN is neither read nor set. CI gains a Windows job.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXLKbmpkRS5DedisNs9VNp

Assisted-by: Claude Opus 5.5
@endel
endel merged commit 8a43f01 into main Oct 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant