github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 mutexContended(l *mutex) bool { 27 return false 28 } 29 30 func lock(l *mutex) { 31 lockWithRank(l, getLockRank(l)) 32 } 33 34 func lock2(l *mutex) { 35 if l.key == mutex_locked { 36 // js/wasm is single-threaded so we should never 37 // observe this. 38 throw("self deadlock") 39 } 40 gp := getg() 41 if gp.m.locks < 0 { 42 throw("lock count") 43 } 44 gp.m.locks++ 45 l.key = mutex_locked 46 } 47 48 func unlock(l *mutex) { 49 unlockWithRank(l) 50 } 51 52 func unlock2(l *mutex) { 53 if l.key == mutex_unlocked { 54 throw("unlock of unlocked lock") 55 } 56 gp := getg() 57 gp.m.locks-- 58 if gp.m.locks < 0 { 59 throw("lock count") 60 } 61 l.key = mutex_unlocked 62 } 63 64 // One-time notifications. 65 66 type noteWithTimeout struct { 67 gp *g 68 deadline int64 69 } 70 71 var ( 72 notes = make(map[*note]*g) 73 notesWithTimeout = make(map[*note]noteWithTimeout) 74 ) 75 76 func noteclear(n *note) { 77 n.key = note_cleared 78 } 79 80 func notewakeup(n *note) { 81 // gp := getg() 82 if n.key == note_woken { 83 throw("notewakeup - double wakeup") 84 } 85 cleared := n.key == note_cleared 86 n.key = note_woken 87 if cleared { 88 goready(notes[n], 1) 89 } 90 } 91 92 func notesleep(n *note) { 93 throw("notesleep not supported by js") 94 } 95 96 func notetsleep(n *note, ns int64) bool { 97 throw("notetsleep not supported by js") 98 return false 99 } 100 101 // same as runtimeĀ·notetsleep, but called on user g (not g0) 102 func notetsleepg(n *note, ns int64) bool { 103 gp := getg() 104 if gp == gp.m.g0 { 105 throw("notetsleepg on g0") 106 } 107 108 if ns >= 0 { 109 deadline := nanotime() + ns 110 delay := ns/1000000 + 1 // round up 111 if delay > 1<<31-1 { 112 delay = 1<<31 - 1 // cap to max int32 113 } 114 115 id := scheduleTimeoutEvent(delay) 116 mp := acquirem() 117 notes[n] = gp 118 notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline} 119 releasem(mp) 120 121 gopark(nil, nil, waitReasonSleep, traceBlockSleep, 1) 122 123 clearTimeoutEvent(id) // note might have woken early, clear timeout 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, traceBlockGeneric, 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 // TODO(drchase): need to understand if write barriers are really okay in this context. 149 // 150 //go:yeswritebarrierrec 151 func checkTimeouts() { 152 now := nanotime() 153 // TODO: map iteration has the write barriers in it; is that okay? 154 for n, nt := range notesWithTimeout { 155 if n.key == note_cleared && now >= nt.deadline { 156 n.key = note_timeout 157 goready(nt.gp, 1) 158 } 159 } 160 } 161 162 // events is a stack of calls from JavaScript into Go. 163 var events []*event 164 165 type event struct { 166 // g was the active goroutine when the call from JavaScript occurred. 167 // It needs to be active when returning to JavaScript. 168 gp *g 169 // returned reports whether the event handler has returned. 170 // When all goroutines are idle and the event handler has returned, 171 // then g gets resumed and returns the execution to JavaScript. 172 returned bool 173 } 174 175 type timeoutEvent struct { 176 id int32 177 // The time when this timeout will be triggered. 178 time int64 179 } 180 181 // diff calculates the difference of the event's trigger time and x. 182 func (e *timeoutEvent) diff(x int64) int64 { 183 if e == nil { 184 return 0 185 } 186 187 diff := x - idleTimeout.time 188 if diff < 0 { 189 diff = -diff 190 } 191 return diff 192 } 193 194 // clear cancels this timeout event. 195 func (e *timeoutEvent) clear() { 196 if e == nil { 197 return 198 } 199 200 clearTimeoutEvent(e.id) 201 } 202 203 // The timeout event started by beforeIdle. 204 var idleTimeout *timeoutEvent 205 206 // beforeIdle gets called by the scheduler if no goroutine is awake. 207 // If we are not already handling an event, then we pause for an async event. 208 // If an event handler returned, we resume it and it will pause the execution. 209 // beforeIdle either returns the specific goroutine to schedule next or 210 // indicates with otherReady that some goroutine became ready. 211 // TODO(drchase): need to understand if write barriers are really okay in this context. 212 // 213 //go:yeswritebarrierrec 214 func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) { 215 delay := int64(-1) 216 if pollUntil != 0 { 217 // round up to prevent setTimeout being called early 218 delay = (pollUntil-now-1)/1e6 + 1 219 if delay > 1e9 { 220 // An arbitrary cap on how long to wait for a timer. 221 // 1e9 ms == ~11.5 days. 222 delay = 1e9 223 } 224 } 225 226 if delay > 0 && (idleTimeout == nil || idleTimeout.diff(pollUntil) > 1e6) { 227 // If the difference is larger than 1 ms, we should reschedule the timeout. 228 idleTimeout.clear() 229 230 idleTimeout = &timeoutEvent{ 231 id: scheduleTimeoutEvent(delay), 232 time: pollUntil, 233 } 234 } 235 236 if len(events) == 0 { 237 // TODO: this is the line that requires the yeswritebarrierrec 238 go handleAsyncEvent() 239 return nil, true 240 } 241 242 e := events[len(events)-1] 243 if e.returned { 244 return e.gp, false 245 } 246 return nil, false 247 } 248 249 var idleStart int64 250 251 func handleAsyncEvent() { 252 idleStart = nanotime() 253 pause(getcallersp() - 16) 254 } 255 256 // clearIdleTimeout clears our record of the timeout started by beforeIdle. 257 func clearIdleTimeout() { 258 idleTimeout.clear() 259 idleTimeout = nil 260 } 261 262 // pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered. 263 func pause(newsp uintptr) 264 265 // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds. 266 // It returns a timer id that can be used with clearTimeoutEvent. 267 // 268 //go:wasmimport gojs runtime.scheduleTimeoutEvent 269 func scheduleTimeoutEvent(ms int64) int32 270 271 // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent. 272 // 273 //go:wasmimport gojs runtime.clearTimeoutEvent 274 func clearTimeoutEvent(id int32) 275 276 // handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package 277 // and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript. 278 // When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine 279 // is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript. 280 func handleEvent() { 281 sched.idleTime.Add(nanotime() - idleStart) 282 283 e := &event{ 284 gp: getg(), 285 returned: false, 286 } 287 events = append(events, e) 288 289 if !eventHandler() { 290 // If we did not handle a window event, the idle timeout was triggered, so we can clear it. 291 clearIdleTimeout() 292 } 293 294 // wait until all goroutines are idle 295 e.returned = true 296 gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1) 297 298 events[len(events)-1] = nil 299 events = events[:len(events)-1] 300 301 // return execution to JavaScript 302 idleStart = nanotime() 303 pause(getcallersp() - 16) 304 } 305 306 // eventHandler retrieves and executes handlers for pending JavaScript events. 307 // It returns true if an event was handled. 308 var eventHandler func() bool 309 310 //go:linkname setEventHandler syscall/js.setEventHandler 311 func setEventHandler(fn func() bool) { 312 eventHandler = fn 313 }