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