diff --git a/lib/wasm/wasm_exec.js b/lib/wasm/wasm_exec.js index af7e28f5f48..94c88045afd 100644 --- a/lib/wasm/wasm_exec.js +++ b/lib/wasm/wasm_exec.js @@ -115,6 +115,12 @@ this._pendingEvent = null; this._scheduledTimeouts = new Map(); this._nextCallbackTimeoutID = 1; + this._cleanup = null; + this._registry = new FinalizationRegistry((func, id) => { + if (this._cleanup) { + this._cleanup(id); + } + }) const setInt64 = (addr, v) => { this.mem.setUint32(addr + 0, v, true); @@ -563,12 +569,17 @@ _makeFuncWrapper(id) { const go = this; - return function () { + const f = function () { const event = { id: id, this: this, args: arguments }; go._pendingEvent = event; go._resume(); return event.result; }; + // Should never remove the cleanup function + if (id !== 1) { + this._registry.register(f, id); + } + return f; } } })(); diff --git a/src/syscall/js/func.go b/src/syscall/js/func.go index 53a4d79a95e..60d4944282e 100644 --- a/src/syscall/js/func.go +++ b/src/syscall/js/func.go @@ -9,7 +9,8 @@ package js import "sync" var ( - funcsMu sync.Mutex + funcsMu sync.Mutex + // funcID 1 is reserved for func garbage collection callback. funcs = make(map[uint32]func(Value, []Value) any) nextFuncID uint32 = 1 ) @@ -36,8 +37,6 @@ type Func struct { // API, which requires the event loop, like fetch (http.Client), will cause an // immediate deadlock. Therefore a blocking function should explicitly start a // new goroutine. -// -// Func.Release must be called to free up resources when the function will not be invoked any more. func FuncOf(fn func(this Value, args []Value) any) Func { funcsMu.Lock() id := nextFuncID @@ -50,13 +49,9 @@ func FuncOf(fn func(this Value, args []Value) any) Func { } } -// Release frees up resources allocated for the function. -// The function must not be invoked after calling Release. -// It is allowed to call Release while the function is still running. +// Release is a no-op method, the function is automatically released +// when it is garbage collected on the JavaScript side. func (c Func) Release() { - funcsMu.Lock() - delete(funcs, c.id) - funcsMu.Unlock() } // setEventHandler is defined in the runtime package. @@ -64,6 +59,19 @@ func setEventHandler(fn func() bool) func init() { setEventHandler(handleEvent) + + // Set func garbage collection callback. + cleanup := FuncOf(func(this Value, args []Value) any { + id := uint32(args[0].Int()) + funcsMu.Lock() + delete(funcs, id) + funcsMu.Unlock() + return Undefined() + }) + if cleanup.id != 1 { + panic("bad function id for cleanup") + } + jsGo.Set("_cleanup", cleanup) } // handleEvent retrieves the pending event (window._pendingEvent) and calls the js.Func on it.