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