Skip to content

Windows: Support native Windows x64 with MSVC, MASM, and WSAPoll. v1.9.2 - #40

Merged
winlinvip merged 37 commits into
ossrs:srsfrom
winlinvip:st-win-reviewed
Oct 4, 2026
Merged

winlinvip merged 37 commits into
ossrs:srsfrom
winlinvip:st-win-reviewed

Conversation

@winlinvip

@winlinvip winlinvip commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

This PR ports State Threads to native Windows x64. It builds with the Visual Studio toolchain (cl, ml64, and lib) through GNU make and the same Makefile, and uses no Cygwin, MinGW, WSL, or POSIX emulation layer. It needs Windows 10 1809 or Windows Server 2019 or later.

make win64-debug          # or win64-optimized
make win64-debug-utest && ./obj/st_utest.exe
./auto/tools.sh

Linux, macOS, and Cygwin64 are unchanged: their code paths and their preprocessed sources are the same as before, and make -n prints the same commands for every POSIX target. Cygwin64 is now marked deprecated in the README.

Build

  • The Makefile gets a WIN64 platform with the win64-debug, win64-optimized, and win64-debug-utest targets. They build the static library obj/libst.a with MSVC.
  • public.h is portable to MSVC and keeps the POSIX names for callers. md.h and common.h get a WIN64 branch.
  • cmake/CMakeLists.txt generates a Visual Studio solution for debugging the utest and the helloworld, porting, and verify tools. The Makefile stays the main build.
  • MSVC AddressSanitizer works with make win64-debug EXTRA_CFLAGS="-DMD_ASAN -fsanitize=address".

Coroutines

  • md_win64.asm implements the context switch in MASM for the Windows x64 ABI. It saves the nonvolatile registers, including xmm6 to xmm15.
  • New threads start in an assembly entry, not after the context save.
  • Each switch sets the TIB stack bounds to the coroutine stack. SEH, and so C++ exceptions, reject frames outside those bounds.
  • Coroutine stacks are allocated with VirtualAlloc, with red zones.
  • docs/win64_coroutine.md explains the design.

I/O and events

  • st_init starts Winsock, and st_destroy cleans it up. The library links ws2_32.lib by itself.
  • The socket I/O calls use Winsock, and Winsock errors map to errno.
  • The event system on Windows is WSAPoll. ST_EVENTSYS_SELECT and ST_EVENTSYS_ALT both select it.
  • ST on Windows supports sockets only: st_open, pipes, FIFOs, signals, and rlimits stay unsupported. The porting notes list what works differently.

@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.01%. Comparing base (e4e208c) to head (1993302).

Additional details and impacted files
@@           Coverage Diff           @@
##              srs      #40   +/-   ##
=======================================
  Coverage   96.01%   96.01%           
=======================================
  Files           8        8           
  Lines        1129     1129           
  Branches      287      287           
=======================================
  Hits         1084     1084           
  Partials       45       45           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

winlinvip and others added 3 commits October 3, 2026 14:12
…optimized, and win64-debug-utest targets to the Makefile.

The WIN64 section builds the static library with MSVC (cl and lib) through GNU make, run from Git Bash or PowerShell with the MSVC environment on PATH. It is separate from CYGWIN64, which is unchanged.

- New variables CC_OUT, AR_OUT, DBG_FLAGS, and LINK_OBJ carry what differs on MSVC: cl ignores -o and needs -Fo, lib needs -OUT:, debug builds use -Z7 -Od, and obj is a directory junction (mklink /J) because Git Bash ln -s copies instead of linking.
- WIN64 uses -nologo -W4, RANLIB is true, and no event system or assembly is defined yet; the ST sources compile with cl only after the portable headers and stubs of the next tasks.
- win64-debug-utest builds the library and then runs the utest Makefile with OS=WIN64, which is ported in a later task.
- Ignore the WIN64_*_DBG and WIN64_*_OPT build directories.

Verified that make -n prints identical commands before and after this change for every POSIX target (linux, darwin, and cygwin64 debug, optimized, utest, and gcov, and clean), and that win64-debug and win64-optimized build, archive, and link obj with a probe source from both Git Bash and PowerShell, and that clean removes the junction.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mes for callers.

On _WIN32 or _MSC_VER, st.h includes winsock2.h and ws2tcpip.h instead of the
POSIX headers, and defines ssize_t, mode_t, struct iovec, and struct msghdr with
the POSIX field names, so SRS and the utest compile unchanged. struct pollfd and
the POLL* names come from winsock. Detection uses compiler macros, not WIN64,
because programs that include st.h do not pass -DWIN64; Cygwin GCC defines
neither, so the POSIX and CYGWIN64 paths are unchanged. The POSIX include block
is kept verbatim in the #else branch.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on.h.

md.h gets a WIN64 branch next to CYGWIN64, which is unchanged: __thread maps
to __declspec(thread), the jmpbuf uses 36 long long slots on WIN64 because MSVC
long is 32-bit (LLP64), MD_GET_SP is the 64-bit rsp slot 8 of the Win64 layout,
MD_GET_UTIME uses QueryPerformanceCounter, and getpagesize uses GetSystemInfo.
common.h and sched.c skip unistd.h, sys/time.h, and fcntl.h on WIN64, and
st_thread_create stores the stack pointer through intptr_t instead of long, so
it is no longer truncated on Windows. On POSIX the preprocessed sources are
identical except for that cast, which keeps the same value.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
winlinvip and others added 25 commits October 3, 2026 16:35
…SVC.

make win64-debug and win64-optimized now build libst.a, and a program that
uses every public function links against it. The Windows internals are stubs
that fail at runtime: md_win64.c has _st_md_cxt_save and _st_md_cxt_restore
that abort with "context switch not ported" (replaced by assembly later);
event.c has a win64-stub default event system whose operations fail with
ENOSYS; io.c stubs ioctl, fcntl, close, read, write, readv, writev, recvmsg,
sendmsg, _st_io_init, and st_open with ENOSYS and brings ws2_32.lib for the
winsock calls. md.h turns on MALLOC_STACK and MD_NO_PROTECT and maps
random/srandom to rand/srand on WIN64. POSIX headers are skipped only on
WIN64; the preprocessed LINUX, DARWIN, and CYGWIN64 sources and the POSIX make
commands are unchanged.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…indows WIN64.

make win64-debug-utest now builds gtest and every utest source with cl -W4 and
links st_utest.exe against libst.a and ws2_32.lib; it starts and fails its
first assertion, as expected while the Windows internals are stubs.
utest/Makefile gets a WIN64 section (cl, lib, -Fo, -OUT:, -Fe, .exe,
NOMINMAX) and output-flag variables whose POSIX values give the same commands
as before.

st_utest.hpp gets Windows test helpers: the stream pair is a stub that fails
with ENOSYS until a loopback TCP pair replaces it, send, recv, close and
would-block use winsock, setsockopt and getsockopt take any pointer, and
__thread, __builtin_frame_address, getpagesize and usleep map to MSVC and
Win32. POSIX-only includes, and the tests about POSIX features (fork, signals,
pipes, pthread, mmap, fcntl, getrlimit, st_open, Unix sockets and CMSG), are
skipped on _WIN32: 131 of 186 tests build on Windows. Removing the _WIN32
branches gives back the previous sources byte for byte, so POSIX tests and
what they assert are unchanged.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new windows job runs on windows-latest. A pwsh step finds Visual Studio
with vswhere, runs vcvars64.bat, and passes INCLUDE, LIB, LIBPATH, and only
the PATH directories vcvars64.bat adds to the next steps, so no third-party
action is needed. A Git Bash step installs GNU make with choco only if the
runner has none, then builds win64-debug-utest and win64-optimized and checks
that st_utest.exe and libst.a exist. It does not run st_utest, which needs the
context switch and event system that are not ported yet.

The final job now also requires the windows job. The utest and coverage jobs
are unchanged.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct on native Windows.

cmake/CMakeLists.txt gets an MSVC branch for the WIN64 platform of the Makefile, which stays the main
build: no bash, ST_DIR from get_filename_component, -DWIN64 -DDEBUG -DNOMINMAX, st.h copied to the build
directory so the obj junction of make is left alone, md_win64.asm with ASM_MASM when it exists and the
md_win64.c stub before, Google Test with the shared CRT, ws2_32, and st_utest as the Visual Studio startup
project. The tools are not built on Windows yet. On a Windows host the project does not enable the GNU
ASM language, and non-MSVC compilers are rejected. The Linux and macOS paths are unchanged: the diff only
adds lines, and the MSVC branch returns before them.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_win64.asm.

md_win64.asm replaces the md_win64.c stub that aborted. _st_md_cxt_save and
_st_md_cxt_restore keep the contract of the other platforms, and save the whole
Win64 callee-saved set: rbx, rbp, rdi, rsi, r12-r15, rsp, pc, xmm6-xmm15, MXCSR
and the x87 control word, plus the TIB stack bounds (StackBase, StackLimit,
DeallocationStack), so C++ exceptions and stack walks work on coroutine stacks.
The layout matches MD_GET_SP (slot 8) and the 288-byte jmpbuf of md.h.

The Makefile assembles it with ml64 -nologo -c through a %.asm rule only for
WIN64, so POSIX make commands are unchanged. CMake always uses ASM_MASM now.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
st_thread_create now sets the stack bounds the OS keeps for a new thread through
MD_INIT_STACK_BOUNDS, defined only for WIN64 in md.h: slots 10-12 of the jmpbuf,
which md_win64.asm restores into the TIB, get the stack top as StackBase and the
stack bottom as StackLimit and DeallocationStack. Before, a new thread inherited
its creator's bounds, so a C++ exception thrown on a coroutine stack was not
caught, and __chkstk could probe the wrong stack for frames over a page.

Other platforms do not define the macro, so their sched.c is unchanged after
preprocessing.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…native Windows x64.

On WIN64, stacks no longer fall back to MALLOC_STACK. stk.c allocates each
stack segment with VirtualAlloc and releases it with VirtualFree, and in a
DEBUG build protects the lower and upper red zones with VirtualProtect
(PAGE_NOACCESS), restoring them before the segment is freed, the same as
mmap and mprotect on POSIX. With MALLOC_STACK, md.h still defines
MD_NO_PROTECT on WIN64, because VirtualProtect rounds a heap address down
to its page and would protect heap memory next to the stack. Linux, macOS,
and Cygwin64 preprocess to the same code.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e sleep timeout.

The WIN64 event system now initializes and its dispatch sleeps until the
earliest sleeper is due (rounded up to milliseconds, INFINITE with no
sleepers, like select() with no descriptors), because Windows select() with
no sockets fails instead of sleeping. ST_EVENTSYS_DEFAULT and SELECT give the
select-like backend and ST_EVENTSYS_ALT is accepted with the same dispatch
(D8), so st_get_eventsys reports what was asked for. _st_io_init succeeds on
WIN64 with no SIGPIPE or rlimit and reports the 2^24 per-process handle limit
from st_getfdlimit. Descriptor polling still fails with ENOSYS until the
WSAPoll backend. Linux, macOS, and Cygwin code paths are unchanged.

st_init now succeeds on Windows, so the utest runs: 69 of 131 pass; the 62
failures are socket I/O, which is not ported yet.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… not after the context save.

st_thread_create saves the context and patches its SP, so a new thread used
to resume after _st_md_cxt_save in the frame of st_thread_create on its new
stack. It worked under /Od, /O2, and /GL, but only by luck of codegen: at /O2
and with LTCG, MSVC inlines _st_thread_main there and reads the TLS base and
_st_this_vp from registers loaded before the save, which survive only
because they are callee-saved and so restored from the jmpbuf.

On WIN64, MD_INIT_THREAD_ENTRY now points the PC (slot 9) at
_st_md_thread_start in md_win64.asm and the SP (slot 8) at a null return
address, 8 mod 16 as at a function entry. The entry has unwind info, calls
_st_thread_main with the home space, and ends stack walks at address 0.
sched.c calls the macro under #ifdef, so Linux, macOS, and Cygwin are
unchanged and still use the save-then-patch-SP trick.

The utest passes 69 of 131 on Windows with the library built /Od, /O2, and
/O2 /GL, the same tests as before; the failures are socket I/O, not ported yet.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion on coroutine stacks.

CoroutineTest.LiveValuesSurviveSwitches keeps seven integers and six doubles live across
st_usleep(0) in four coroutines and compares them to a run without switches. The function is
optimized by itself (MSVC #pragma optimize, GCC optimize attribute), so the values live in
callee-saved registers such as rdi, rsi and xmm6-xmm15 even in the -O0 utest build.

CoroutineTest.DeepRecursionOnDefaultStack and DeepRecursionWithLargeFramesOnLargeStack recurse
with a buffer per frame, switch at the deepest frame, and check every buffer on the way back.
ExceptionTest.ThrowFromDeepRecursionWithLargeFrames throws from the bottom of such a recursion
in three coroutines and checks every destructor runs in order.

On Windows the new tests fail when the context switch misses rdi/rsi, xmm6-xmm15 or the TIB
stack bounds, or when the requested stack size is ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tests.

The windows job now runs obj/st_utest.exe after the win64-debug-utest build,
in its own step with a 10-minute timeout, and builds win64-optimized in a
separate step afterwards, because the obj junction then points to the
optimized build. The coroutine, sync, key, sleep, and exception tests pass on
Windows; the socket I/O tests do not until the Windows event system and I/O
are ported, so a gtest negative filter skips the Io*, Poll, Tcp, and epoll
suites and the seven other tests that open sockets. The filter is to be
removed when socket I/O is ported. A failing selected test fails the job.

The utest, coverage, and final jobs are unchanged.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Winsock reports errors by WSAGetLastError(), but ST's I/O loops and callers
such as SRS check errno. _st_win64_errno() maps a Winsock error to errno in
one place: WSAEWOULDBLOCK is EAGAIN, or EINPROGRESS for connect, WSAETIMEDOUT
is ETIMEDOUT, WSAEINTR is EINTR, and so on; an error with no equivalent is
EIO. The Winsock calls io.c makes (accept, connect, getsockopt, recvfrom,
sendto) go through wrappers that set errno on failure, and accept returns
the SOCKET as an int. Only the WIN64 code changes.

Adds WinErrnoTest utests for the mapping table, connect, unknown errors, and
errors from real non-blocking sockets.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ive Windows.

Winsock needs WSAStartup before any socket call, and ST's callers such as
SRS do not call it. On WIN64, _st_io_init starts Winsock 2.2 once per OS
thread's ST, and st_destroy cleans up that start through _st_io_destroy, so
st_init and st_destroy are balanced. A failed start sets errno. Only the
WIN64 code changes.

Adds WinsockTest utests: st_init starts Winsock; real SOCKET values of TCP,
UDP, and IPv6 sockets round-trip through int and still work as the socket;
and, in a child process, another OS thread's st_init/st_destroy leaves
Winsock running while the last st_destroy stops it.

With sockets now created, the tests' CRT close() on a socket hits the CRT
invalid parameter handler and kills the suite, so the socket closes that
build on Windows go through st_utest_close, which is close() on POSIX.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On WIN64, the POSIX calls of io.c, which were ENOSYS stubs, now use Winsock,
for sockets only: ioctl(FIONBIO) is ioctlsocket; fcntl supports F_GETFL
(checks for a socket) and F_SETFL with O_NONBLOCK; close is closesocket;
read and write are recv and send; readv and writev are WSARecv and WSASend,
with each iovec copied to a WSABUF (the field order differs); recvmsg is
WSARecvMsg, loaded once with WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER),
and sendmsg is WSASendMsg. Both fall back to WSARecv and WSASend on a TCP
stream, which POSIX allows and the msg functions do not take. A truncated
datagram returns the bytes that fit, with MSG_TRUNC for recvmsg, as on POSIX,
where Winsock fails with WSAEMSGSIZE; recvfrom does the same. Control data
is Winsock's layout. st_open, SIGPIPE, and rlimits stay unsupported. The
calls are external and declared in common.h so the utest can call them.
Only the WIN64 code changes.

Adds WinIoTest utests with real loopback sockets for each call: non-blocking
mode by ioctl and fcntl, stream read and write with end of stream, not
connected errors, scatter and gather with more vectors than the stack holds,
datagram sendmsg and recvmsg with the sender address, MSG_PEEK, truncated
datagrams, an oversized datagram, sendmsg and recvmsg on a stream, and close.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e Windows.

Windows has no socketpair, so st_utest_stream_pair, which the utest uses for
two connected sockets ST can wait on, was a stub failing with ENOSYS. It is now
a loopback TCP connection of two blocking sockets: a listener on 127.0.0.1 port
0, a connect, and an accept that keeps only the client's own connection (by
peer address), with TCP_NODELAY on both ends so small writes are not delayed as
on a Unix-domain socketpair. Errors map to errno through _st_win64_errno. The
POSIX helper is unchanged.

New UtestPairTest tests: data both ways (small and 64 KB) and end of stream on
close, 64 pairs open at once each connected to its own end, and on Windows that
both ends are loopback TCP peers of each other with Nagle off and blocking.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows event system now polls sockets with WSAPoll, for both
ST_EVENTSYS_DEFAULT and ST_EVENTSYS_ALT, named "wsapoll". Each dispatch
builds the WSAPOLLFD array from the pollsets on the I/O queue, so there
are no per-descriptor counts: closing a descriptor fails with EBUSY while
a pollset holds it. With nothing to poll it sleeps until the next timer.
An error or hang-up wakes every waiter of the descriptor, as epoll does.
st_poll refuses a negative descriptor (the Winsock SOCKET is unsigned),
POLLPRI (WSAPoll has none, so EINVAL as on kqueue), and a part of
Winsock's POLLIN such as POLLRDNORM. Opening anything but an open socket
fails with EBADF.

io.c: getsockopt(SO_ERROR), which st_connect reads, gives the POSIX errno
instead of the Winsock code, and a receive on a datagram socket that got
an ICMP port unreachable fails with ECONNREFUSED instead of ECONNRESET.

New WsaPollTest tests: the backend name, and a refused connect reported
by WSAPoll with ECONNREFUSED before the timeout. The Windows CI job now
skips only the 9 tests that Windows TCP still fails: 8 IoWriteTest tests
(Winsock takes a whole non-blocking send larger than SO_SNDBUF) and
IoConnectTest.ConnectRefusedWhenNobodyListens (SYN retries take about 2
seconds, longer than its 100 ms timeout).

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Winsock takes a whole non-blocking send, however large, while the send
buffer holds less than SO_SNDBUF, so a single large write to a slow player
never waits. On Windows, three IoWriteTest tests now fill the send and
receive buffers first, so the write under test waits and sees the reset or
the stop as on POSIX: WriteFailsWhenPlayerResetsMidStream,
WritevFailsWhenPlayerResetsMidStream, and
WritevInterruptedWhenConnectionStops. ST reads its clock again after the
fill, which sleeps outside ST.

Winsock never sends part of a non-blocking send: it takes the whole buffer
or fails with WSAEWOULDBLOCK and sends nothing. So the five tests that need
a partial count skip on Windows: WriteWaitsForSlowPlayer,
WritevWaitsForSlowPlayer, WritevWaitsForSlowPlayerWithMergedWrite,
WritevResidResumesAfterTimeout, and WritevTimesOutAndLosesTheSentCount.
IoConnectTest.ConnectRefusedWhenNobodyListens also skips on Windows, which
retries a refused connect for about 2 seconds, longer than its timeout.
io.c is unchanged.

The Windows CI job now runs the whole st_utest without a filter.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TcpTest.EchoManyConcurrentConnections starts an echo server on loopback
and 1000 client coroutines. Every client connects and waits in a read for
a go byte, which the server sends only when all clients are connected and
every accepted connection has an echo coroutine waiting in a read, so all
2000 sockets are polled at the same time. Each client then sends three
messages of its own, each in two writes, and checks every echo; the test
checks the counts and the total bytes echoed.

It is for correctness, not speed, and is portable. On POSIX it raises the
soft descriptor limit toward the hard limit, and runs fewer clients only if
the hard limit is too low. On Windows it passes with the WSAPoll backend; a
backend that ignores descriptors past 1024 fails it.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The MSVC asan runtime (clang_rt.asan_dynamic, MSVC 14.44) exports
__sanitizer_start_switch_fiber and __sanitizer_finish_switch_fiber, so
MD_ASAN works with no library change: the whole utest passes on Windows
built with -DMD_ASAN -fsanitize=address, with the library at -Od and -O2.
Without MD_ASAN, ASan ignores __asan_handle_no_return on coroutine stacks
and the ExceptionTest suite fails with a false stack-buffer-underflow.

The Makefile documents the Windows asan command. Use-after-return
(-fsanitize-address-use-after-return) is not usable: MSVC 14.44 reports
false positives with it even in a small program without ST.

PrimordialStackTest.DescribesTheMainThreadStack now runs on Windows too,
reading the main thread's stack bounds with GetCurrentThreadStackLimits.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each tool Makefile detects native Windows by uname -s (MINGW64_NT-* in
Git Bash, MSYS_NT-* from PowerShell) and builds with cl -W4, -Fe for the
executable, a .exe suffix, and win64-debug for the library. The POSIX
commands are unchanged (make -n on Linux and Darwin gives the same output).

porting prints _WIN64, _M_X64, _M_ARM64, and _MSC_VER, and includes
execinfo.h only off Windows. The CMake MSVC build adds the three tools too.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README.md: build native Windows x64 with GNU make in the MSVC
environment (x64 Native Tools Command Prompt, then Git Bash), with the
win64-debug and win64-optimized targets, and with MSVC ASAN and
MD_ASAN; run the utest with win64-debug-utest, and generate a Visual
Studio solution with CMake. The old "Windows native build is
unsupported" remark is gone, and the Cygwin build is replaced by a remark
that Cygwin64 is deprecated, use native Windows.

docs/notes.html: native Windows porting notes: the WIN64 platform, the
MASM context switch with the TIB stack bounds, VirtualAlloc stacks,
QueryPerformanceCounter, the WSAPoll event system, Winsock I/O with int
descriptors and errno mapping, and what is unsupported or differs
(st_open, SIGPIPE, rlimit, all-or-nothing sends, refused connect delay,
no /CETCOMPAT, ASAN use-after-return).

Bump the version to 1.9.2 in Makefile, public.h, and st.spec, with a
CHANGELOG.md entry for the Windows port.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
auto/tools.sh builds win64-debug and runs <name>.exe under Git Bash on Windows
(uname -s MINGW* or MSYS*), and takes the tools to run as arguments, every
tool by default. Every tool Makefile gets the MSVC block of helloworld and
verify, tool.h builds with cl (Winsock from st.h, closesocket, wsapoll names),
porting accepts Windows x64, and verify.c drops a C4312 cast. The MSVC tool
outputs are ignored, and .sh files keep LF so WSL runs a Windows checkout.

On Windows, helloworld, porting, verify, sync, and thread pass; the others
do not build yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
time: Windows has no fork, so the custom clock child is the tool run again
with the argument custom-clock (_spawnl, waited for its exit code), and the
real clock is QueryPerformanceCounter instead of clock_gettime. key: the key
array is on the heap, because MSVC has no variable length arrays. lifecycle:
the primordial stack comes from GetCurrentThreadStackLimits, and as Windows
has no RLIMIT_NOFILE, the limit check is that st_getfdlimit reports the
per-process handle limit 2^24, as documented. POSIX code is unchanged except
the heap key array.

On Windows, time, key, lifecycle, thread, and sync pass with select and alt.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The POSIX headers are only included off Windows, the raw socket is cast to
int, and is_open asks getsockopt(SO_TYPE) on Windows, where a closed socket
fails with WSAENOTSOCK, reported as EBADF like fcntl(F_GETFD) on POSIX.

Winsock never sends part of a non-blocking send: it takes the whole buffer or
fails with WSAEWOULDBLOCK. So on Windows, resid() skips only its three
partial-write-on-timeout checks (st_write_resid, st_writev_resid, then
st_write failing with ETIME), as the utest skips its partial-send tests.
Every other check runs, including the residual counts at EOF.

On Windows, tcp passes with select and alt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows has no rlimit, so it does not support the check that ST raises
RLIMIT_NOFILE in st_init. Skip check_fdlimit on Windows with a comment,
instead of the Windows-only st_getfdlimit() == 1<<24 check from the
previous commit. Every other lifecycle check stays.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
winlinvip and others added 8 commits October 4, 2026 15:13
Include sys/uio.h only off _WIN32, since st.h brings Winsock 2 and the
iovec and msghdr types on Windows. Cast the SOCKET from socket() to int,
and close a raw socket with tool_close_socket, which is closesocket on
Windows. Every udp check runs on Windows, over IPv4 and IPv6, with both
event systems; no check changed and no ST library change.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ST on native Windows takes sockets only, so pipes and socketpair in the
tools become connected socket pairs there (D26). tools/tool.h adds
tool_pipe (pipe, or a loopback TCP pair on Windows), tool_socketpair
(socketpair, or an AF_UNIX stream pair through a path in the temp folder
on Windows), and tool_write (write, or send with EAGAIN on Windows). On
POSIX each is the real call. poll.c uses them, includes fcntl.h and
poll.h only off _WIN32, and skips only the O_NONBLOCK read-back with
fcntl(F_GETFL) on Windows, which cannot read back a socket's mode. Every
other check runs unchanged, with both event systems; no ST library change.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stress.c includes netinet/tcp.h, sys/resource.h, and sys/uio.h only off
_WIN32; on Windows st.h brings TCP_NODELAY and struct iovec from
Winsock 2. Windows has no rlimit, so the getrlimit and setrlimit setup
that raises the descriptor limit before st_init is under #ifndef _WIN32
with a comment (D25); the check that st_getfdlimit() covers the count
still runs everywhere. Every other check is unchanged and passes with
both event systems, also with a count of 1000; no ST library change.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Native Windows has no execinfo.h, so the backtrace tool gets the frames with
CaptureStackBackTrace, which walks a coroutine stack with the unwind data and
the TIB stack bounds ST sets, and names them with DbgHelp SymFromAddr in the
glibc form "address (name+0xoffset)". __builtin_return_address maps to
_ReturnAddress for level 0, all the builtin path requires. The checks that bar,
foo and start are on the coroutine stack are unchanged; no ST library change.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ST on native Windows takes sockets only, with no pipes, FIFOs, files
(st_open), or signals, so pipe.c is skipped there as a whole (D27): on
_WIN32 it builds a main that prints "SKIP pipe: <why>" and exits 0, with
a comment; the POSIX tool is unchanged under #else. Its socket
fill-then-drain is covered by the poll and unix tools.

auto/tools.sh shows a tool that prints only "SKIP <name>: <why>" as that
line with its event system, not as OK. No tool prints it on Linux or
macOS, so their output is unchanged.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows AF_UNIX is stream only (D28). unix.c includes sys/uio.h and
sys/un.h only off _WIN32; on Windows the stream paths are in the temp
folder, and unlink and access are the MSVC CRT calls. stream_echo and
pair_stream run unchanged; pair_dgram runs over a connected loopback UDP
pair, which tool_socketpair in tools/tool.h now makes for SOCK_DGRAM on
Windows, a channel swap like D26, with every check unchanged. The
dgram_echo test (AF_UNIX datagram paths) and the connect-to-missing-path
ENOENT check (Windows gives ECONNREFUSED) are skipped on Windows with a
comment (D25). socket() is cast to int and closed with tool_close_socket,
which is close() off Windows. No ST library change.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… them for Windows.

The windows job in .github/workflows/test.yml runs ./auto/tools.sh after
the utest, in the ST default build and with EXTRA_CFLAGS=-DMALLOC_STACK
(the SRS build), as the Linux and macOS jobs do. Each step uses Git Bash
in the MSVC environment and fails on any tool failure; the utest,
coverage, and final jobs are unchanged.

README.md gets a Windows: Tools section: how to run auto/tools.sh, the
pipe and socketpair channels as sockets, the pipe tool SKIP lines, and
the checks Windows does not support.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…utine stacks.

The 15 integration tools are C, so none threw, and only the utest
ExceptionTest covered the TIB stack bounds that the Windows context
switch must restore for SEH (D30). tools/exception/exception.cpp throws
and catches on coroutine stacks: a throw in a coroutine, unwinding many
frames in order after a switch, coroutines that switch before they throw
on the default and a 32 KB stack, frames larger than a page down most of
a 1 MB stack, a destructor that switches while unwound, an exception_ptr
rethrown by another coroutine, and a throw in main while coroutines wait
deep in their frames. On Windows it also raises and catches SEH itself:
RaiseException with __finally and __except, and an access violation.

auto/tools.sh relinks a C++ tool through <name>.cpp. README.md describes
the tool in Windows: Tools. No ST library change. Without the TIB restore
in _st_md_cxt_restore, the tool dies with 0xE06D7363, and its SEH checks
alone with 0xE0535431.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@winlinvip winlinvip changed the title Update W Oct 4, 2026
@winlinvip winlinvip changed the title W Windows: Support native Windows x64 with MSVC, MASM, and WSAPoll. v1.9.2 Oct 4, 2026
@winlinvip winlinvip changed the title Windows: Support native Windows x64 with MSVC, MASM, and WSAPoll. v1.9.2 Windows: Support native Windows x64 with MSVC, MASM, and WSAPoll. v1.9.2 Oct 4, 2026
…ieces.

StreamPairCarriesBothWays sent 64 KB on a blocking end before reading the
other end, in one thread. A macOS socketpair holds only 8 KB each way
(net.local.stream.sendspace and recvspace), so send blocked forever and
the darwin-arm64 CI job hung; Linux socketpair buffers and the Windows
loopback TCP pair are large enough to hide it. The test now sends the
64 KB in 4 KB pieces, each read at the other end before the next, in both
directions, so it still carries more than a buffer each way.

Verified on macOS and Linux, each with the utest and auto/tools.sh in the
default, MALLOC_STACK, and ASAN builds. No ST library change.

---------

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@winlinvip
winlinvip merged commit 099b714 into ossrs:srs Oct 4, 2026
8 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