Commit 9014ec4
runtime,syscall,internal/poll,os,sync: wasip1 poll_oneoff scheduler integration + net.FileListener
On wasip1 today every syscall.Read/Write blocks the entire wasm module — the
cooperative scheduler invokes poll_oneoff only for sleep/timer wakeups, and
there's no path from the net package to a working TCP server. This change
fixes both: it threads poll_oneoff through the scheduler's idle path so a
goroutine doing FD I/O parks instead of blocking the module, and it provides
enough internal/poll / os / syscall surface that upstream Go's
net.FileListener / net.FileConn works on a host-pre-opened TCP socket.
End to end:
$ tinygo build -target=wasip1 -o tcpecho.wasm ./tcpecho.go
$ wasmtime run -Spreview2=false -Stcplisten=127.0.0.1:9999 ./tcpecho.wasm &
listening on FD 3
$ echo hello | nc 127.0.0.1 9999
hello # echoed by the wasm
Concurrent connections work too — multiple nc clients hit the server back-to-
back; both got accepted and echoed while the cooperative scheduler kept
running goroutines parked on sock_recv. The tcpecho.go source is the minimal
upstream-Go-idiomatic TCP echo server, no TinyGo net override required:
f := os.NewFile(3, "tcplisten")
ln, _ := net.FileListener(f)
for {
c, _ := ln.Accept()
go func(c net.Conn) { defer c.Close(); io.Copy(c, c) }(c)
}
Architecture:
The cooperative scheduler's idle path now calls poll_oneoff with one combined
subscription array: a clock subscription for the next timer/sleep deadline,
plus one FD subscription per goroutine that's parked waiting on I/O.
syscall.Read/Write ─EAGAIN─► internal/poll registry ─► task.Pause()
│
▼
scheduler idle ──► pollIO(timeoutNs)
│
├─ build subs: [clock, fd1, fd2, …]
├─ poll_oneoff(...)
└─ wake matched tasks → run queue
Upstream net's net.FileListener(f) flow plumbs through these layers:
1. (*os.File).PollFD() returns a cached *poll.FD stored in a new
pfd pollFD field on the shared file struct. pollFD is a per-target
alias — *poll.FD on wasip1, literal struct{} (zero bytes, non-
trailing) on every other target.
2. (*poll.FD).Copy() increments a SysFile refcount so f.Close() and
ln.Close() cooperatively release the syscall FD.
3. Upstream net/file_wasip1.go calls fd_fdstat_get_type (linknamed into
our syscall) to detect FILETYPE_SOCKET_STREAM.
4. Listener.Accept → (*poll.FD).Accept → syscall.Accept → sock_accept
wasmimport. EAGAIN parks the goroutine via the runtime netpoll
registry.
5. Conn.Read/Write see isSocket() == true (cached SysFile.Filetype) and
dispatch to sock_recv / sock_send direct wasmimports with park-on-
EAGAIN + deadline support.
6. Conn.Close triggers Shutdown (→ sock_shutdown) and refcount-aware
Close.
Non-goals (deferred):
- net.Dial("tcp", ...), net.Listen("tcp", ...) — wasip1 has no
sock_connect / bind / listen (only sock_accept / recv / send /
shutdown). Outbound TCP requires the wasi-sockets proposal (preview2+)
or a runtime-specific extension. The relevant stubs return ENOSYS so
callers see a clean error.
- UDP / PacketConn — upstream's filePacketConn returns ENOPROTOOPT on
wasip1.
- DNS resolution — FileListener / FileConn paths bypass the resolver
entirely.
- wasip2 — uses pollable resources, structurally different. Future PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 1a1506e commit 9014ec4
18 files changed
Lines changed: 1436 additions & 47 deletions
File tree
- loader
- src
- internal/poll
- os
- runtime
- sync
- syscall
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
246 | 246 | | |
247 | 247 | | |
248 | 248 | | |
| 249 | + | |
249 | 250 | | |
250 | 251 | | |
251 | 252 | | |
| |||
| 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 | + | |
0 commit comments