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// +build js,wasm 6 7package runtime 8 9import ( 10 _ "unsafe" 11) 12 13// js/wasm has no support for threads yet. There is no preemption. 14// Waiting for a mutex is implemented by allowing other goroutines 15// to run until the mutex gets unlocked. 16 17const ( 18 mutex_unlocked = 0 19 mutex_locked = 1 20 21 note_cleared = 0 22 note_woken = 1 23 note_timeout = 2 24 25 active_spin = 4 26 active_spin_cnt = 30 27 passive_spin = 1 28) 29 30func lock(l *mutex) { 31 for l.key == mutex_locked { 32 mcall(gosched_m) 33 } 34 l.key = mutex_locked 35} 36 37func unlock(l *mutex) { 38 if l.key == mutex_unlocked { 39 throw("unlock of unlocked lock") 40 } 41 l.key = mutex_unlocked 42} 43 44// One-time notifications. 45 46type noteWithTimeout struct { 47 gp *g 48 deadline int64 49} 50 51var ( 52 notes = make(map[*note]*g) 53 notesWithTimeout = make(map[*note]noteWithTimeout) 54) 55 56func noteclear(n *note) { 57 n.key = note_cleared 58} 59 60func notewakeup(n *note) { 61 // gp := getg() 62 if n.key == note_woken { 63 throw("notewakeup - double wakeup") 64 } 65 cleared := n.key == note_cleared 66 n.key = note_woken 67 if cleared { 68 goready(notes[n], 1) 69 } 70} 71 72func notesleep(n *note) { 73 throw("notesleep not supported by js") 74} 75 76func notetsleep(n *note, ns int64) bool { 77 throw("notetsleep not supported by js") 78 return false 79} 80 81// same as runtime·notetsleep, but called on user g (not g0) 82func notetsleepg(n *note, ns int64) bool { 83 gp := getg() 84 if gp == gp.m.g0 { 85 throw("notetsleepg on g0") 86 } 87 88 if ns >= 0 { 89 deadline := nanotime() + ns 90 delay := ns/1000000 + 1 // round up 91 if delay > 1<<31-1 { 92 delay = 1<<31 - 1 // cap to max int32 93 } 94 95 id := scheduleTimeoutEvent(delay) 96 mp := acquirem() 97 notes[n] = gp 98 notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline} 99 releasem(mp) 100 101 gopark(nil, nil, waitReasonSleep, traceEvNone, 1) 102 103 clearTimeoutEvent(id) // note might have woken early, clear timeout 104 mp = acquirem() 105 delete(notes, n) 106 delete(notesWithTimeout, n) 107 releasem(mp) 108 109 return n.key == note_woken 110 } 111 112 for n.key != note_woken { 113 mp := acquirem() 114 notes[n] = gp 115 releasem(mp) 116 117 gopark(nil, nil, waitReasonZero, traceEvNone, 1) 118 119 mp = acquirem() 120 delete(notes, n) 121 releasem(mp) 122 } 123 return true 124} 125 126// checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline. 127func checkTimeouts() { 128 now := nanotime() 129 for n, nt := range notesWithTimeout { 130 if n.key == note_cleared && now >= nt.deadline { 131 n.key = note_timeout 132 goready(nt.gp, 1) 133 } 134 } 135} 136 137var returnedEventHandler *g 138 139func init() { 140 // At the toplevel we need an extra goroutine that handles asynchronous events. 141 initg := getg() 142 go func() { 143 returnedEventHandler = getg() 144 goready(initg, 1) 145 146 gopark(nil, nil, waitReasonZero, traceEvNone, 1) 147 returnedEventHandler = nil 148 149 pause(getcallersp() - 16) 150 }() 151 gopark(nil, nil, waitReasonZero, traceEvNone, 1) 152} 153 154// beforeIdle gets called by the scheduler if no goroutine is awake. 155// We resume the event handler (if available) which will pause the execution. 156func beforeIdle() bool { 157 if returnedEventHandler != nil { 158 goready(returnedEventHandler, 1) 159 return true 160 } 161 return false 162} 163 164// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered. 165func pause(newsp uintptr) 166 167// scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds. 168// It returns a timer id that can be used with clearTimeoutEvent. 169func scheduleTimeoutEvent(ms int64) int32 170 171// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent. 172func clearTimeoutEvent(id int32) 173 174func handleEvent() { 175 prevReturnedEventHandler := returnedEventHandler 176 returnedEventHandler = nil 177 178 checkTimeouts() 179 eventHandler() 180 181 returnedEventHandler = getg() 182 gopark(nil, nil, waitReasonZero, traceEvNone, 1) 183 184 returnedEventHandler = prevReturnedEventHandler 185 186 pause(getcallersp() - 16) 187} 188 189var eventHandler func() 190 191//go:linkname setEventHandler syscall/js.setEventHandler 192func setEventHandler(fn func()) { 193 eventHandler = fn 194} 195