Go version
go version go1.24.0 darwin/arm64
Output of go env
in your module/workspace:
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/kyon/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/kyon/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/nm/73wzznv13ld2vcfxd12d24k40000gn/T/go-build1372434015=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/kyon/Code/Tiledmedia/core/go.mod'
GOMODCACHE='/Users/kyon/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/kyon/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/kyon/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.0.darwin-arm64'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/kyon/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/kyon/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.0.darwin-arm64/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.0'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
GOOS=js GOARCH=wasm go build -o main.wasm main.go && goexec 'http.ListenAndServe(\`:8080\`, http.FileServer(http.Dir(`.`)))'
A minimal repro is provided here: https://github.com/kyoncal/go-wasm-memory-leak
What did you see happen?
Running the sample program using the command provided leads to a memory leak in both Chrome and Firefox. The value of the pointer passed to the wasm function goFunction
is never cleared. In the sample program provided, the memory usage will grow forever.
What did you expect to see?
I expect to see the memory get cleared eventually, however it grows forever. There are work arounds that allow the memory to get garbage collected, such as wrapping the pointer in a JS object, and setting the field to undefined
afterwards, however, this is certainly not intended behavior.
Comment From: gabyhelp
Related Issues
- wasm: does not return memory to the OS #59061 (closed)
- wasm: large memory usage with hard-coded map/array #42979
- syscall/js: implement GC of refs #35111 (closed)
- runtime: gc does not work with `wasmexport` and "void" functions #69584 (closed)
- runtime: wasm: fatal error: all goroutines are asleep - deadlock! #41310 (closed)
- wasm: program allocs too much memory #35390 (closed)
- misc/wasm: `this._inst.exports.getsp` is not a function #28924 (closed)
- misc/wasm: wasm_exec.js not protected against growth in a few try/catch cases #45433 (closed)
- unknown ptrSize for $GOARCH "wasm" #40543 (closed)
- cmd/compile: uncaught RuntimeError: memory access out of bounds #38093 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: dmitshur
CC @golang/wasm.
Comment From: Zxilly
The Go
object in wasm_exec.js
retains all JavaScript values referenced by WASM internally. These values are only released after the Go garbage collector determines they are no longer needed. Since WASM is single-threaded, the GC process appears to be less active.
For now, you can add a defer debug.FreeOSMemory()
to force memory release. I will look into whether there are any improvements that can be made to the GC mechanism in WASM.
//go:build js && wasm
package main
import (
"runtime/debug"
"syscall/js"
)
func main() {
goFunction := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
defer debug.FreeOSMemory()
return nil
})
js.Global().Set("goFunction", goFunction)
select {}
}