Commit b3fce39
authored
fix: Make NativeServer link and run on Windows, unblocking Windows Native CI (#641)
**Description**
uni's Scala Native test binary cannot link on Windows — at all, today,
on `main`:
```
error LNK2019: unresolved external symbol poll
error LNK2019: unresolved external symbol scalanative_pollerr
error LNK2019: unresolved external symbol scalanative_pollhup
error LNK2019: unresolved external symbol scalanative_pollin
error LNK2019: unresolved external symbol scalanative_pollnval
referenced in function ...wvlet.uni.http.NativeServerTest...
fatal error LNK1120: 5 unresolved externals
```
Scala Native's
[`posixlib/poll.c`](https://github.com/scala-native/scala-native/blob/v0.5.12/posixlib/src/main/resources/scala-native/poll.c)
wraps its whole body in `#if defined(__unix__) || (defined(__APPLE__) &&
defined(__MACH__))`, so `poll` and the `scalanative_poll*` constant
accessors don't exist there. `NativeSocket` referenced
`scalanative.posix.poll`, every native HTTP test starts a server, and so
the whole binary was unlinkable.
That gap is why uni had **no Windows Scala Native CI**, and therefore
why #640's root cause — v2026.1.17 shipping a POSIX-only `#include
<dlfcn.h>` in `uni_curl_shim.c` — broke every downstream Windows build
instead of failing here.
**The fix.** Exactly three calls differ on Windows. Everything else uni
uses (`socket`, `bind`, `listen`, `accept`, `recv`, `send`,
`setsockopt`, `shutdown`) posixlib already maps onto winsock — the
failed link proved it, by resolving all of them and only missing `poll`.
So `uni_socket_shim.c` takes those three, behind an `@extern object
SocketShim`:
| | POSIX | Windows |
|---|---|---|
| `uni_socket_startup()` | nothing | `WSAStartup`, once, via
`InitOnceExecuteOnce` |
| `uni_socket_wait_readable(fd, ms)` | `poll` | `WSAPoll` |
| `uni_socket_close(fd)` | `close` | `closesocket` |
Three things here are easy to get wrong, so they're in the
[ADR](https://github.com/wvlet/uni/blob/fix/native-server-windows-poll/adr/2026-07-08-native-socket-shim.md):
- **The split has to be in C.** Scala Native has no per-OS source
directory (`.native` is per-*platform*), and DCE keeps every *reachable*
branch — so a runtime `if (isWindows)` in Scala links both sides and
still fails on `scalanative_pollin`. Merely *referencing* `posix.poll`
is the break. (`scalanative.windows.WinSocketApi.WSAPoll` is the
mirror-image problem: referencing it breaks the POSIX link on `ws2_32`.)
- **`#pragma comment(lib, "ws2_32.lib")`, not `@link("ws2_32")`.** Scala
Native compiles this `.c` into every downstream binary, including ones
that never open a socket. A bare `WSAPoll` reference with no guaranteed
`-lws2_32` breaks those links — precisely the #622 trap. The pragma
embeds the dependency in the object's linker directives, so it travels
with the object; a Scala-level `@link` gets dropped by DCE. Scala
Native's own `posixlib/sys/socket.c` does exactly this.
- **`WSAStartup` had no other caller.** Winsock rejects every `socket()`
with `WSANOTINITIALISED` until it runs. Scala Native calls it from
`WinSocketApiOps.init()`, reached only by javalib's `java.net` — which
uni's posixlib sockets never touch. And `close()` on a socket *links* on
Windows (`oldnames.lib` → `_close`) but only knows CRT file descriptors,
so it silently leaks the socket; that applies to the error-cleanup paths
in `bindAndListen`/`connect` too, not just the public `close`.
**Verification.**
- The shim's contract is exercised directly by a C harness over a
`socketpair`: idle → timeout, data pending → readable, drained →
timeout, **data+hangup → readable** (drain before reporting hangup),
hangup-only → readable/EOF, closed fd → error. All pass, and
`uni_socket_startup()` is idempotent.
- `projectNative/test` passes locally on macOS: 65/65, including
`NativeWebSocketClientTest`'s heartbeat test, which is the poll-timeout
path.
- CI now runs a real **`Scala Native (Windows)`** job — restored in
place of #640's standalone `clang` compile, which was only ever a
stand-in for this. It exercises `uni_socket_shim.c`
(`WSAStartup`/`WSAPoll`/`closesocket`) *and* `uni_curl_shim.c`
(`GetProcAddress` over `EnumProcessModules`) at runtime, not just at
compile time. Gated as before: every push to `main`, plus PRs touching
native code.
`check-curl-shim.sh` stays as a step in the Linux Native job. It is
still irreplaceable: `build.sbt` passes `-lcurl` unconditionally, so no
Scala Native job on any OS can notice the curl shim regrowing a libcurl
symbol reference — only a consumer without `-lcurl` breaks. Its
Windows/`llvm-nm` branch is dropped now that the real job covers Windows
compilation.
**Related Issue/Task**
Follow-up to #640. Removes the "a real Windows Native job is impossible"
caveat that PR had to record, and closes uni's Windows Scala Native
coverage gap at its source.
**Checklist**
- [x] This pull request focuses on a single task.
- [x] The change does not contain security credentials
🤖 Generated with [Claude Code](https://claude.com/claude-code)1 parent 319de15 commit b3fce39
7 files changed
Lines changed: 481 additions & 115 deletions
File tree
- .github
- scripts
- workflows
- adr
- uni/.native/src/main
- resources/scala-native
- scala/wvlet/uni/http
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
4 | | - | |
| 3 | + | |
5 | 4 | | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
10 | 8 | | |
11 | | - | |
12 | | - | |
13 | | - | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
14 | 12 | | |
15 | | - | |
16 | | - | |
17 | | - | |
| 13 | + | |
| 14 | + | |
18 | 15 | | |
19 | 16 | | |
20 | 17 | | |
| |||
24 | 21 | | |
25 | 22 | | |
26 | 23 | | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
| 24 | + | |
33 | 25 | | |
34 | 26 | | |
35 | 27 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
137 | 137 | | |
138 | 138 | | |
139 | 139 | | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
151 | 146 | | |
| 147 | + | |
| 148 | + | |
152 | 149 | | |
153 | 150 | | |
154 | 151 | | |
155 | 152 | | |
156 | | - | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
157 | 220 | | |
158 | | - | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
159 | 229 | | |
160 | 230 | | |
161 | 231 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
77 | 81 | | |
78 | 82 | | |
79 | 83 | | |
| |||
113 | 117 | | |
114 | 118 | | |
115 | 119 | | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | 120 | | |
138 | 121 | | |
139 | 122 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
0 commit comments