github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/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 // TODO(drchase): need to understand if write barriers are really okay in this context. 148 // 149 //go:yeswritebarrierrec 150 func checkTimeouts() { 151 now := nanotime() 152 // TODO: map iteration has the write barriers in it; is that okay? 153 for n, nt := range notesWithTimeout { 154 if n.key == note_cleared && now >= nt.deadline { 155 n.key = note_timeout 156 goready(nt.gp, 1) 157 } 158 } 159 } 160 161 // events is a stack of calls from JavaScript into Go. 162 var events []*event 163 164 type event struct { 165 // g was the active goroutine when the call from JavaScript occurred. 166 // It needs to be active when returning to JavaScript. 167 gp *g 168 // returned reports whether the event handler has returned. 169 // When all goroutines are idle and the event handler has returned, 170 // then g gets resumed and returns the execution to JavaScript. 171 returned bool 172 } 173 174 // The timeout event started by beforeIdle. 175 var idleID int32 176 177 // beforeIdle gets called by the scheduler if no goroutine is awake. 178 // If we are not already handling an event, then we pause for an async event. 179 // If an event handler returned, we resume it and it will pause the execution. 180 // beforeIdle either returns the specific goroutine to schedule next or 181 // indicates with otherReady that some goroutine became ready. 182 // TODO(drchase): need to understand if write barriers are really okay in this context. 183 // 184 //go:yeswritebarrierrec 185 func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) { 186 delay := int64(-1) 187 if pollUntil != 0 { 188 delay = pollUntil - now 189 } 190 191 if delay > 0 { 192 clearIdleID() 193 if delay < 1e6 { 194 delay = 1 195 } else if delay < 1e15 { 196 delay = delay / 1e6 197 } else { 198 // An arbitrary cap on how long to wait for a timer. 199 // 1e9 ms == ~11.5 days. 200 delay = 1e9 201 } 202 idleID = scheduleTimeoutEvent(delay) 203 } 204 205 if len(events) == 0 { 206 // TODO: this is the line that requires the yeswritebarrierrec 207 go handleAsyncEvent() 208 return nil, true 209 } 210 211 e := events[len(events)-1] 212 if e.returned { 213 return e.gp, false 214 } 215 return nil, false 216 } 217 218 func handleAsyncEvent() { 219 pause(getcallersp() - 16) 220 } 221 222 // clearIdleID clears our record of the timeout started by beforeIdle. 223 func clearIdleID() { 224 if idleID != 0 { 225 clearTimeoutEvent(idleID) 226 idleID = 0 227 } 228 } 229 230 // pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered. 231 func pause(newsp uintptr) 232 233 // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds. 234 // It returns a timer id that can be used with clearTimeoutEvent. 235 func scheduleTimeoutEvent(ms int64) int32 236 237 // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent. 238 func clearTimeoutEvent(id int32) 239 240 // handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package 241 // and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript. 242 // When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine 243 // is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript. 244 func handleEvent() { 245 e := &event{ 246 gp: getg(), 247 returned: false, 248 } 249 events = append(events, e) 250 251 eventHandler() 252 253 clearIdleID() 254 255 // wait until all goroutines are idle 256 e.returned = true 257 gopark(nil, nil, waitReasonZero, traceEvNone, 1) 258 259 events[len(events)-1] = nil 260 events = events[:len(events)-1] 261 262 // return execution to JavaScript 263 pause(getcallersp() - 16) 264 } 265 266 var eventHandler func() 267 268 //go:linkname setEventHandler syscall/js.setEventHandler 269 func setEventHandler(fn func()) { 270 eventHandler = fn 271 }