1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
9 import _ "unsafe" // for go:linkname
11 // js/wasm has no support for threads yet. There is no preemption.
27 lockWithRank(l, getLockRank(l))
30 func lock2(l *mutex) {
31 if l.key == mutex_locked {
32 // js/wasm is single-threaded so we should never
34 throw("self deadlock")
44 func unlock(l *mutex) {
48 func unlock2(l *mutex) {
49 if l.key == mutex_unlocked {
50 throw("unlock of unlocked lock")
57 l.key = mutex_unlocked
60 // One-time notifications.
62 type noteWithTimeout struct {
68 notes = make(map[*note]*g)
69 notesWithTimeout = make(map[*note]noteWithTimeout)
72 func noteclear(n *note) {
76 func notewakeup(n *note) {
78 if n.key == note_woken {
79 throw("notewakeup - double wakeup")
81 cleared := n.key == note_cleared
88 func notesleep(n *note) {
89 throw("notesleep not supported by js")
92 func notetsleep(n *note, ns int64) bool {
93 throw("notetsleep not supported by js")
97 // same as runtimeĀ·notetsleep, but called on user g (not g0)
98 func notetsleepg(n *note, ns int64) bool {
101 throw("notetsleepg on g0")
105 deadline := nanotime() + ns
106 delay := ns/1000000 + 1 // round up
108 delay = 1<<31 - 1 // cap to max int32
111 id := scheduleTimeoutEvent(delay)
114 notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline}
117 gopark(nil, nil, waitReasonSleep, traceBlockSleep, 1)
119 clearTimeoutEvent(id) // note might have woken early, clear timeout
123 delete(notesWithTimeout, n)
126 return n.key == note_woken
129 for n.key != note_woken {
134 gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1)
143 // checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline.
144 // TODO(drchase): need to understand if write barriers are really okay in this context.
146 //go:yeswritebarrierrec
147 func checkTimeouts() {
149 // TODO: map iteration has the write barriers in it; is that okay?
150 for n, nt := range notesWithTimeout {
151 if n.key == note_cleared && now >= nt.deadline {
158 // events is a stack of calls from JavaScript into Go.
162 // g was the active goroutine when the call from JavaScript occurred.
163 // It needs to be active when returning to JavaScript.
165 // returned reports whether the event handler has returned.
166 // When all goroutines are idle and the event handler has returned,
167 // then g gets resumed and returns the execution to JavaScript.
171 type timeoutEvent struct {
173 // The time when this timeout will be triggered.
177 // diff calculates the difference of the event's trigger time and x.
178 func (e *timeoutEvent) diff(x int64) int64 {
183 diff := x - idleTimeout.time
190 // clear cancels this timeout event.
191 func (e *timeoutEvent) clear() {
196 clearTimeoutEvent(e.id)
199 // The timeout event started by beforeIdle.
200 var idleTimeout *timeoutEvent
202 // beforeIdle gets called by the scheduler if no goroutine is awake.
203 // If we are not already handling an event, then we pause for an async event.
204 // If an event handler returned, we resume it and it will pause the execution.
205 // beforeIdle either returns the specific goroutine to schedule next or
206 // indicates with otherReady that some goroutine became ready.
207 // TODO(drchase): need to understand if write barriers are really okay in this context.
209 //go:yeswritebarrierrec
210 func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) {
213 // round up to prevent setTimeout being called early
214 delay = (pollUntil-now-1)/1e6 + 1
216 // An arbitrary cap on how long to wait for a timer.
217 // 1e9 ms == ~11.5 days.
222 if delay > 0 && (idleTimeout == nil || idleTimeout.diff(pollUntil) > 1e6) {
223 // If the difference is larger than 1 ms, we should reschedule the timeout.
226 idleTimeout = &timeoutEvent{
227 id: scheduleTimeoutEvent(delay),
232 if len(events) == 0 {
233 // TODO: this is the line that requires the yeswritebarrierrec
234 go handleAsyncEvent()
238 e := events[len(events)-1]
245 func handleAsyncEvent() {
246 pause(getcallersp() - 16)
249 // clearIdleTimeout clears our record of the timeout started by beforeIdle.
250 func clearIdleTimeout() {
255 // pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
256 func pause(newsp uintptr)
258 // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
259 // It returns a timer id that can be used with clearTimeoutEvent.
261 //go:wasmimport gojs runtime.scheduleTimeoutEvent
262 func scheduleTimeoutEvent(ms int64) int32
264 // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
266 //go:wasmimport gojs runtime.clearTimeoutEvent
267 func clearTimeoutEvent(id int32)
269 // handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package
270 // and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript.
271 // When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine
272 // is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript.
278 events = append(events, e)
281 // If we did not handle a window event, the idle timeout was triggered, so we can clear it.
285 // wait until all goroutines are idle
287 gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1)
289 events[len(events)-1] = nil
290 events = events[:len(events)-1]
292 // return execution to JavaScript
293 pause(getcallersp() - 16)
296 // eventHandler retrieves and executes handlers for pending JavaScript events.
297 // It returns true if an event was handled.
298 var eventHandler func() bool
300 //go:linkname setEventHandler syscall/js.setEventHandler
301 func setEventHandler(fn func() bool) {