| // Copyright 2018 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build js && wasm | |
| package runtime | |
| import ( | |
| "internal/runtime/sys" | |
| _ "unsafe" // for go:linkname | |
| ) | |
| // js/wasm has no support for threads yet. There is no preemption. | |
| const ( | |
| mutex_unlocked = 0 | |
| mutex_locked = 1 | |
| note_cleared = 0 | |
| note_woken = 1 | |
| note_timeout = 2 | |
| active_spin = 4 | |
| active_spin_cnt = 30 | |
| passive_spin = 1 | |
| mutexMLocksDelta = 16 | |
| ) | |
| type mWaitList struct{} | |
| func lockVerifyMSize() {} | |
| func mutexContended(l *mutex) bool { | |
| return false | |
| } | |
| func lock(l *mutex) { | |
| lockWithRank(l, getLockRank(l)) | |
| } | |
| func lock2(l *mutex) { | |
| if l.key == mutex_locked { | |
| // js/wasm is single-threaded so we should never | |
| // observe this. | |
| throw("self deadlock") | |
| } | |
| gp := getg() | |
| if gp.m.locks < 0 { | |
| throw("lock count") | |
| } | |
| gp.m.locks += mutexMLocksDelta | |
| l.key = mutex_locked | |
| } | |
| func unlock(l *mutex) { | |
| unlockWithRank(l) | |
| } | |
| func unlock2(l *mutex) { | |
| if l.key == mutex_unlocked { | |
| throw("unlock of unlocked lock") | |
| } | |
| gp := getg() | |
| gp.m.locks -= mutexMLocksDelta | |
| if gp.m.locks < 0 { | |
| throw("lock count") | |
| } | |
| l.key = mutex_unlocked | |
| } | |
| // One-time notifications. | |
| // Linked list of notes with a deadline. | |
| var allDeadlineNotes *note | |
| func noteclear(n *note) { | |
| n.status = note_cleared | |
| } | |
| func notewakeup(n *note) { | |
| if n.status == note_woken { | |
| throw("notewakeup - double wakeup") | |
| } | |
| cleared := n.status == note_cleared | |
| n.status = note_woken | |
| if cleared { | |
| goready(n.gp, 1) | |
| } | |
| } | |
| func notesleep(n *note) { | |
| throw("notesleep not supported by js") | |
| } | |
| func notetsleep(n *note, ns int64) bool { | |
| throw("notetsleep not supported by js") | |
| return false | |
| } | |
| // same as runtimeĀ·notetsleep, but called on user g (not g0) | |
| func notetsleepg(n *note, ns int64) bool { | |
| gp := getg() | |
| if gp == gp.m.g0 { | |
| throw("notetsleepg on g0") | |
| } | |
| if ns >= 0 { | |
| deadline := nanotime() + ns | |
| delay := ns/1000000 + 1 // round up | |
| if delay > 1<<31-1 { | |
| delay = 1<<31 - 1 // cap to max int32 | |
| } | |
| id := scheduleTimeoutEvent(delay) | |
| n.gp = gp | |
| n.deadline = deadline | |
| if allDeadlineNotes != nil { | |
| allDeadlineNotes.allprev = n | |
| } | |
| n.allnext = allDeadlineNotes | |
| allDeadlineNotes = n | |
| gopark(nil, nil, waitReasonSleep, traceBlockSleep, 1) | |
| clearTimeoutEvent(id) // note might have woken early, clear timeout | |
| n.gp = nil | |
| n.deadline = 0 | |
| if n.allprev != nil { | |
| n.allprev.allnext = n.allnext | |
| } | |
| if allDeadlineNotes == n { | |
| allDeadlineNotes = n.allnext | |
| } | |
| n.allprev = nil | |
| n.allnext = nil | |
| return n.status == note_woken | |
| } | |
| for n.status != note_woken { | |
| n.gp = gp | |
| gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1) | |
| n.gp = nil | |
| } | |
| return true | |
| } | |
| // checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline. | |
| func checkTimeouts() { | |
| now := nanotime() | |
| for n := allDeadlineNotes; n != nil; n = n.allnext { | |
| if n.status == note_cleared && n.deadline != 0 && now >= n.deadline { | |
| n.status = note_timeout | |
| goready(n.gp, 1) | |
| } | |
| } | |
| } | |
| // events is a stack of calls from JavaScript into Go. | |
| var events []*event | |
| type event struct { | |
| // g was the active goroutine when the call from JavaScript occurred. | |
| // It needs to be active when returning to JavaScript. | |
| gp *g | |
| // returned reports whether the event handler has returned. | |
| // When all goroutines are idle and the event handler has returned, | |
| // then g gets resumed and returns the execution to JavaScript. | |
| 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. | |
| var idleTimeout *timeoutEvent | |
| // 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 an event handler returned, we resume it and it will pause the execution. | |
| // beforeIdle either returns the specific goroutine to schedule next or | |
| // indicates with otherReady that some goroutine became ready. | |
| // TODO(drchase): need to understand if write barriers are really okay in this context. | |
| // | |
| //go:yeswritebarrierrec | |
| func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) { | |
| delay := int64(-1) | |
| if pollUntil != 0 { | |
| // round up to prevent setTimeout being called early | |
| delay = (pollUntil-now-1)/1e6 + 1 | |
| if delay > 1e9 { | |
| // An arbitrary cap on how long to wait for a timer. | |
| // 1e9 ms == ~11.5 days. | |
| delay = 1e9 | |
| } | |
| } | |
| 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 { | |
| // TODO: this is the line that requires the yeswritebarrierrec | |
| go handleAsyncEvent() | |
| return nil, true | |
| } | |
| e := events[len(events)-1] | |
| if e.returned { | |
| return e.gp, false | |
| } | |
| return nil, false | |
| } | |
| var idleStart int64 | |
| func handleAsyncEvent() { | |
| idleStart = nanotime() | |
| pause(sys.GetCallerSP() - 16) | |
| } | |
| // clearIdleTimeout clears our record of the timeout started by beforeIdle. | |
| func clearIdleTimeout() { | |
| idleTimeout.clear() | |
| idleTimeout = nil | |
| } | |
| // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds. | |
| // It returns a timer id that can be used with clearTimeoutEvent. | |
| // | |
| //go:wasmimport gojs runtime.scheduleTimeoutEvent | |
| func scheduleTimeoutEvent(ms int64) int32 | |
| // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent. | |
| // | |
| //go:wasmimport gojs runtime.clearTimeoutEvent | |
| func clearTimeoutEvent(id int32) | |
| // handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package | |
| // and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript. | |
| // When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine | |
| // is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript. | |
| func handleEvent() { | |
| sched.idleTime.Add(nanotime() - idleStart) | |
| e := &event{ | |
| gp: getg(), | |
| returned: false, | |
| } | |
| events = append(events, e) | |
| if !eventHandler() { | |
| // If we did not handle a window event, the idle timeout was triggered, so we can clear it. | |
| clearIdleTimeout() | |
| } | |
| // wait until all goroutines are idle | |
| e.returned = true | |
| gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1) | |
| events[len(events)-1] = nil | |
| events = events[:len(events)-1] | |
| // return execution to JavaScript | |
| idleStart = nanotime() | |
| pause(sys.GetCallerSP() - 16) | |
| } | |
| // 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 | |
| func setEventHandler(fn func() bool) { | |
| eventHandler = fn | |
| } |