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. 4 5// +build js,wasm 6 7package runtime 8 9import ( 10 _ "unsafe" 11) 12 13// js/wasm has no support for threads yet. There is no preemption. 14 15const ( 16 mutex_unlocked = 0 17 mutex_locked = 1 18 19 note_cleared = 0 20 note_woken = 1 21 note_timeout = 2 22 23 active_spin = 4 24 active_spin_cnt = 30 25 passive_spin = 1 26) 27 28func lock(l *mutex) { 29 if l.key == mutex_locked { 30 // js/wasm is single-threaded so we should never 31 // observe this. 32 throw("self deadlock") 33 } 34 gp := getg() 35 if gp.m.locks < 0 { 36 throw("lock count") 37 } 38 gp.m.locks++ 39 l.key = mutex_locked 40} 41 42func unlock(l *mutex) { 43 if l.key == mutex_unlocked { 44 throw("unlock of unlocked lock") 45 } 46 gp := getg() 47 gp.m.locks-- 48 if gp.m.locks < 0 { 49 throw("lock count") 50 } 51 l.key = mutex_unlocked 52} 53 54// One-time notifications. 55 56type noteWithTimeout struct { 57 gp *g 58 deadline int64 59} 60 61var ( 62 notes = make(map[*note]*g) 63 notesWithTimeout = make(map[*note]noteWithTimeout) 64) 65 66func noteclear(n *note) { 67 n.key = note_cleared 68} 69 70func notewakeup(n *note) { 71 // gp := getg() 72 if n.key == note_woken { 73 throw("notewakeup - double wakeup") 74 } 75 cleared := n.key == note_cleared 76 n.key = note_woken 77 if cleared { 78 goready(notes[n], 1) 79 } 80} 81 82func notesleep(n *note) { 83 throw("notesleep not supported by js") 84} 85 86func notetsleep(n *note, ns int64) bool { 87 throw("notetsleep not supported by js") 88 return false 89} 90 91// same as runtime·notetsleep, but called on user g (not g0) 92func notetsleepg(n *note, ns int64) bool { 93 gp := getg() 94 if gp == gp.m.g0 { 95 throw("notetsleepg on g0") 96 } 97 98 if ns >= 0 { 99 deadline := nanotime() + ns 100 delay := ns/1000000 + 1 // round up 101 if delay > 1<<31-1 { 102 delay = 1<<31 - 1 // cap to max int32 103 } 104 105 id := scheduleTimeoutEvent(delay) 106 mp := acquirem() 107 notes[n] = gp 108 notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline} 109 releasem(mp) 110 111 gopark(nil, nil, waitReasonSleep, traceEvNone, 1) 112 113 clearTimeoutEvent(id) // note might have woken early, clear timeout 114 clearIdleID() 115 116 mp = acquirem() 117 delete(notes, n) 118 delete(notesWithTimeout, n) 119 releasem(mp) 120 121 return n.key == note_woken 122 } 123 124 for n.key != note_woken { 125 mp := acquirem() 126 notes[n] = gp 127 releasem(mp) 128 129 gopark(nil, nil, waitReasonZero, traceEvNone, 1) 130 131 mp = acquirem() 132 delete(notes, n) 133 releasem(mp) 134 } 135 return true 136} 137 138// checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline. 139func checkTimeouts() { 140 now := nanotime() 141 for n, nt := range notesWithTimeout { 142 if n.key == note_cleared && now >= nt.deadline { 143 n.key = note_timeout 144 goready(nt.gp, 1) 145 } 146 } 147} 148 149// events is a stack of calls from JavaScript into Go. 150var events []*event 151 152type event struct { 153 // g was the active goroutine when the call from JavaScript occurred. 154 // It needs to be active when returning to JavaScript. 155 gp *g 156 // returned reports whether the event handler has returned. 157 // When all goroutines are idle and the event handler has returned, 158 // then g gets resumed and returns the execution to JavaScript. 159 returned bool 160} 161 162// The timeout event started by beforeIdle. 163var idleID int32 164 165// beforeIdle gets called by the scheduler if no goroutine is awake. 166// If we are not already handling an event, then we pause for an async event. 167// If an event handler returned, we resume it and it will pause the execution. 168func beforeIdle(delay int64) bool { 169 if delay > 0 { 170 clearIdleID() 171 if delay < 1e6 { 172 delay = 1 173 } else if delay < 1e15 { 174 delay = delay / 1e6 175 } else { 176 // An arbitrary cap on how long to wait for a timer. 177 // 1e9 ms == ~11.5 days. 178 delay = 1e9 179 } 180 idleID = scheduleTimeoutEvent(delay) 181 } 182 183 if len(events) == 0 { 184 go handleAsyncEvent() 185 return true 186 } 187 188 e := events[len(events)-1] 189 if e.returned { 190 goready(e.gp, 1) 191 return true 192 } 193 return false 194} 195 196func handleAsyncEvent() { 197 pause(getcallersp() - 16) 198} 199 200// clearIdleID clears our record of the timeout started by beforeIdle. 201func clearIdleID() { 202 if idleID != 0 { 203 clearTimeoutEvent(idleID) 204 idleID = 0 205 } 206} 207 208// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered. 209func pause(newsp uintptr) 210 211// scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds. 212// It returns a timer id that can be used with clearTimeoutEvent. 213func scheduleTimeoutEvent(ms int64) int32 214 215// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent. 216func clearTimeoutEvent(id int32) 217 218// handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package 219// and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript. 220// When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine 221// is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript. 222func handleEvent() { 223 e := &event{ 224 gp: getg(), 225 returned: false, 226 } 227 events = append(events, e) 228 229 eventHandler() 230 231 clearIdleID() 232 233 // wait until all goroutines are idle 234 e.returned = true 235 gopark(nil, nil, waitReasonZero, traceEvNone, 1) 236 237 events[len(events)-1] = nil 238 events = events[:len(events)-1] 239 240 // return execution to JavaScript 241 pause(getcallersp() - 16) 242} 243 244var eventHandler func() 245 246//go:linkname setEventHandler syscall/js.setEventHandler 247func setEventHandler(fn func()) { 248 eventHandler = fn 249} 250