This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Go HTTP Hello World Server + non-blocking I/O test. | |
// | |
// This requires gotip (https://pkg.go.dev/golang.org/dl/gotip) for: | |
// - https://github.com/golang/go/commit/41893389 | |
// - https://github.com/golang/go/commit/c5c21845 | |
// - https://github.com/golang/go/commit/a17de43e | |
// | |
// Compile http.wasm: | |
// | |
// $ GOOS=wasip1 GOARCH=wasm gotip build -o http.wasm http.go | |
// | |
// Alternatively, you can use the http.wasm file attached to the gist, | |
// which was compiled with: | |
// | |
// $ gotip version | |
// go version devel go1.21-b9baf44 Thu Jun 8 01:52:35 2023 +0000 darwin/arm64 | |
// | |
// You can use the wasirun command to run the WebAssembly module: | |
// | |
// $ go install github.com/stealthrocket/wasi-go/cmd/wasirun@latest | |
// $ wasirun --listen 127.0.0.1:8080 http.wasm & | |
// [1] 22810 | |
// $ curl http://127.0.0.1:8080/ | |
// Hello, World! | |
// | |
// wazero (https://wazero.io) also supports non-blocking I/O and pre-opening | |
// sockets: | |
// | |
// $ wazero run --listen 127.0.0.1:8080 http.wasm | |
// | |
// wasmtime (https://wasmtime.dev) also supports non-blocking I/O and | |
// pre-opening sockets: | |
// | |
// $ wasmtime --tcplisten 127.0.0.1:8080 http.wasm | |
// | |
// If additional networking capabilities are required, for example making | |
// outbound connections, see https://github.com/stealthrocket/net | |
package main | |
import ( | |
"errors" | |
"log" | |
"net" | |
"net/http" | |
"os" | |
"syscall" | |
) | |
func main() { | |
if err := run(); err != nil { | |
log.Fatal(err) | |
} | |
} | |
func run() error { | |
l, err := findListener() | |
if err != nil { | |
return err | |
} else if l == nil { | |
return errors.New("no pre-opened sockets available") | |
} | |
defer l.Close() | |
return http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
w.Write([]byte("Hello, World!\n")) | |
})) | |
} | |
func findListener() (net.Listener, error) { | |
// We start looking for pre-opened sockets at fd=3 because 0, 1, and 2 | |
// are reserved for stdio. | |
for preopenFd := 3; ; preopenFd++ { | |
// Pre-opened directories also start at fd=3, so we skip fds that | |
// aren't sockets. Once we reach EBADF we know there are no more | |
// pre-opens. | |
var stat syscall.Stat_t | |
if err := syscall.Fstat(preopenFd, &stat); err != nil { | |
var se syscall.Errno | |
if errors.As(err, &se) && se == syscall.EBADF { | |
err = nil | |
} | |
return nil, err | |
} else if stat.Filetype != syscall.FILETYPE_SOCKET_STREAM { | |
continue | |
} | |
// Try to enable non-blocking mode. WASM doesn't (yet) support | |
// threads which means that blocking system calls are | |
// particularly costly. By enabling non-blocking I/O, we can | |
// let the Go scheduler schedule goroutines while waiting for | |
// I/O. | |
syscall.SetNonblock(preopenFd, true) | |
f := os.NewFile(uintptr(preopenFd), "") | |
defer f.Close() | |
return net.FileListener(f) | |
} | |
} |
Yes, fixed!
Closing of the file created by os.NewFile
often results in closing the preopen file for /
(the root directory).
It looks like we're only using it to do a stat
call, maybe replace with syscall.Fstat
?
I'll look into fstat. For now I've removed the f.Close()
call.
I'm under the impression that os.NewFile
will install a finalizer on the object to automatically close the file if it's garbage collected, so even if we don't explicitly close it the outcome may be similar here.
K that should be fixed.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@chriso is the wasmtime command supposed to be
--tcplisten
instead of--listen
? (I have v8.0.1, in case it changed in a different version)