mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
wasm: remove redundant calls to setTimeout and clearTimeout
The existing implementation clears and recreates Javascript timeouts when Go is called from js, leading to excessive load on the js scheduler. Instead, we should remove redundant calls to clearTimeout and refrain from creating new timeouts if the previous event's timestamp is within 1 millisecond of our target (the js scheduler's max precision) Fixes #56100 Change-Id: I42bbed4c2f1fa6579c1f3aa519b6ed8fc003a20c Reviewed-on: https://go-review.googlesource.com/c/go/+/442995 Reviewed-by: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com> Run-TryBot: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
parent
1dad7ef572
commit
1cfacfbe8a
@ -276,7 +276,7 @@
|
|||||||
this._resume();
|
this._resume();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
|
getInt64(sp + 8),
|
||||||
));
|
));
|
||||||
this.mem.setInt32(sp + 16, id, true);
|
this.mem.setInt32(sp + 16, id, true);
|
||||||
},
|
},
|
||||||
|
@ -117,7 +117,6 @@ func notetsleepg(n *note, ns int64) bool {
|
|||||||
gopark(nil, nil, waitReasonSleep, traceBlockSleep, 1)
|
gopark(nil, nil, waitReasonSleep, traceBlockSleep, 1)
|
||||||
|
|
||||||
clearTimeoutEvent(id) // note might have woken early, clear timeout
|
clearTimeoutEvent(id) // note might have woken early, clear timeout
|
||||||
clearIdleID()
|
|
||||||
|
|
||||||
mp = acquirem()
|
mp = acquirem()
|
||||||
delete(notes, n)
|
delete(notes, n)
|
||||||
@ -169,8 +168,36 @@ type event struct {
|
|||||||
returned bool
|
returned bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type timeoutEvent struct {
|
||||||
|
id int32
|
||||||
|
// The time when this timeout will be triggered.
|
||||||
|
time int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// diff calculates the difference of the event's trigger time and x.
|
||||||
|
func (e *timeoutEvent) diff(x int64) int64 {
|
||||||
|
if e == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := x - idleTimeout.time
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear cancels this timeout event.
|
||||||
|
func (e *timeoutEvent) clear() {
|
||||||
|
if e == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeoutEvent(e.id)
|
||||||
|
}
|
||||||
|
|
||||||
// The timeout event started by beforeIdle.
|
// The timeout event started by beforeIdle.
|
||||||
var idleID int32
|
var idleTimeout *timeoutEvent
|
||||||
|
|
||||||
// beforeIdle gets called by the scheduler if no goroutine is awake.
|
// beforeIdle gets called by the scheduler if no goroutine is awake.
|
||||||
// If we are not already handling an event, then we pause for an async event.
|
// If we are not already handling an event, then we pause for an async event.
|
||||||
@ -183,21 +210,23 @@ var idleID int32
|
|||||||
func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) {
|
func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) {
|
||||||
delay := int64(-1)
|
delay := int64(-1)
|
||||||
if pollUntil != 0 {
|
if pollUntil != 0 {
|
||||||
delay = pollUntil - now
|
// round up to prevent setTimeout being called early
|
||||||
}
|
delay = (pollUntil-now-1)/1e6 + 1
|
||||||
|
if delay > 1e9 {
|
||||||
if delay > 0 {
|
|
||||||
clearIdleID()
|
|
||||||
if delay < 1e6 {
|
|
||||||
delay = 1
|
|
||||||
} else if delay < 1e15 {
|
|
||||||
delay = delay / 1e6
|
|
||||||
} else {
|
|
||||||
// An arbitrary cap on how long to wait for a timer.
|
// An arbitrary cap on how long to wait for a timer.
|
||||||
// 1e9 ms == ~11.5 days.
|
// 1e9 ms == ~11.5 days.
|
||||||
delay = 1e9
|
delay = 1e9
|
||||||
}
|
}
|
||||||
idleID = scheduleTimeoutEvent(delay)
|
}
|
||||||
|
|
||||||
|
if delay > 0 && (idleTimeout == nil || idleTimeout.diff(pollUntil) > 1e6) {
|
||||||
|
// If the difference is larger than 1 ms, we should reschedule the timeout.
|
||||||
|
idleTimeout.clear()
|
||||||
|
|
||||||
|
idleTimeout = &timeoutEvent{
|
||||||
|
id: scheduleTimeoutEvent(delay),
|
||||||
|
time: pollUntil,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(events) == 0 {
|
if len(events) == 0 {
|
||||||
@ -217,12 +246,10 @@ func handleAsyncEvent() {
|
|||||||
pause(getcallersp() - 16)
|
pause(getcallersp() - 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clearIdleID clears our record of the timeout started by beforeIdle.
|
// clearIdleTimeout clears our record of the timeout started by beforeIdle.
|
||||||
func clearIdleID() {
|
func clearIdleTimeout() {
|
||||||
if idleID != 0 {
|
idleTimeout.clear()
|
||||||
clearTimeoutEvent(idleID)
|
idleTimeout = nil
|
||||||
idleID = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
|
// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
|
||||||
@ -250,9 +277,10 @@ func handleEvent() {
|
|||||||
}
|
}
|
||||||
events = append(events, e)
|
events = append(events, e)
|
||||||
|
|
||||||
eventHandler()
|
if !eventHandler() {
|
||||||
|
// If we did not handle a window event, the idle timeout was triggered, so we can clear it.
|
||||||
clearIdleID()
|
clearIdleTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
// wait until all goroutines are idle
|
// wait until all goroutines are idle
|
||||||
e.returned = true
|
e.returned = true
|
||||||
@ -265,9 +293,11 @@ func handleEvent() {
|
|||||||
pause(getcallersp() - 16)
|
pause(getcallersp() - 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventHandler func()
|
// eventHandler retrieves and executes handlers for pending JavaScript events.
|
||||||
|
// It returns true if an event was handled.
|
||||||
|
var eventHandler func() bool
|
||||||
|
|
||||||
//go:linkname setEventHandler syscall/js.setEventHandler
|
//go:linkname setEventHandler syscall/js.setEventHandler
|
||||||
func setEventHandler(fn func()) {
|
func setEventHandler(fn func() bool) {
|
||||||
eventHandler = fn
|
eventHandler = fn
|
||||||
}
|
}
|
||||||
|
@ -60,16 +60,19 @@ func (c Func) Release() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setEventHandler is defined in the runtime package.
|
// setEventHandler is defined in the runtime package.
|
||||||
func setEventHandler(fn func())
|
func setEventHandler(fn func() bool)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
setEventHandler(handleEvent)
|
setEventHandler(handleEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleEvent() {
|
// handleEvent retrieves the pending event (window._pendingEvent) and calls the js.Func on it.
|
||||||
|
// It returns true if an event was handled.
|
||||||
|
func handleEvent() bool {
|
||||||
|
// Retrieve the event from js
|
||||||
cb := jsGo.Get("_pendingEvent")
|
cb := jsGo.Get("_pendingEvent")
|
||||||
if cb.IsNull() {
|
if cb.IsNull() {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
jsGo.Set("_pendingEvent", Null())
|
jsGo.Set("_pendingEvent", Null())
|
||||||
|
|
||||||
@ -77,14 +80,17 @@ func handleEvent() {
|
|||||||
if id == 0 { // zero indicates deadlock
|
if id == 0 { // zero indicates deadlock
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the associated js.Func
|
||||||
funcsMu.Lock()
|
funcsMu.Lock()
|
||||||
f, ok := funcs[id]
|
f, ok := funcs[id]
|
||||||
funcsMu.Unlock()
|
funcsMu.Unlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
Global().Get("console").Call("error", "call to released function")
|
Global().Get("console").Call("error", "call to released function")
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call the js.Func with arguments
|
||||||
this := cb.Get("this")
|
this := cb.Get("this")
|
||||||
argsObj := cb.Get("args")
|
argsObj := cb.Get("args")
|
||||||
args := make([]Value, argsObj.Length())
|
args := make([]Value, argsObj.Length())
|
||||||
@ -92,5 +98,8 @@ func handleEvent() {
|
|||||||
args[i] = argsObj.Index(i)
|
args[i] = argsObj.Index(i)
|
||||||
}
|
}
|
||||||
result := f(this, args)
|
result := f(this, args)
|
||||||
|
|
||||||
|
// Return the result to js
|
||||||
cb.Set("result", result)
|
cb.Set("result", result)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user