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