Skip to content

Commit

Permalink
syscall/js: auto recycle Func
Browse files Browse the repository at this point in the history
  • Loading branch information
Zxilly committed Sep 8, 2024
1 parent 807e01d commit 0f6f58f
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 10 deletions.
13 changes: 12 additions & 1 deletion lib/wasm/wasm_exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
})();
26 changes: 17 additions & 9 deletions src/syscall/js/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
Expand All @@ -50,20 +49,29 @@ 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.
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.
Expand Down

0 comments on commit 0f6f58f

Please sign in to comment.