]> Cypherpunks.ru repositories - gostls13.git/blob - src/runtime/lock_js.go
ae2bb3db4750674246ffcbbe9870fe842ae712ee
[gostls13.git] / src / runtime / lock_js.go
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, traceBlockSleep, 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, traceBlockGeneric, 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, traceBlockGeneric, 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 }