]> Cypherpunks.ru repositories - gostls13.git/blob - src/runtime/lock_js.go
wasm: remove redundant calls to setTimeout and clearTimeout
[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
121                 mp = acquirem()
122                 delete(notes, n)
123                 delete(notesWithTimeout, n)
124                 releasem(mp)
125
126                 return n.key == note_woken
127         }
128
129         for n.key != note_woken {
130                 mp := acquirem()
131                 notes[n] = gp
132                 releasem(mp)
133
134                 gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1)
135
136                 mp = acquirem()
137                 delete(notes, n)
138                 releasem(mp)
139         }
140         return true
141 }
142
143 // checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline.
144 // TODO(drchase): need to understand if write barriers are really okay in this context.
145 //
146 //go:yeswritebarrierrec
147 func checkTimeouts() {
148         now := nanotime()
149         // TODO: map iteration has the write barriers in it; is that okay?
150         for n, nt := range notesWithTimeout {
151                 if n.key == note_cleared && now >= nt.deadline {
152                         n.key = note_timeout
153                         goready(nt.gp, 1)
154                 }
155         }
156 }
157
158 // events is a stack of calls from JavaScript into Go.
159 var events []*event
160
161 type event struct {
162         // g was the active goroutine when the call from JavaScript occurred.
163         // It needs to be active when returning to JavaScript.
164         gp *g
165         // returned reports whether the event handler has returned.
166         // When all goroutines are idle and the event handler has returned,
167         // then g gets resumed and returns the execution to JavaScript.
168         returned bool
169 }
170
171 type timeoutEvent struct {
172         id int32
173         // The time when this timeout will be triggered.
174         time int64
175 }
176
177 // diff calculates the difference of the event's trigger time and x.
178 func (e *timeoutEvent) diff(x int64) int64 {
179         if e == nil {
180                 return 0
181         }
182
183         diff := x - idleTimeout.time
184         if diff < 0 {
185                 diff = -diff
186         }
187         return diff
188 }
189
190 // clear cancels this timeout event.
191 func (e *timeoutEvent) clear() {
192         if e == nil {
193                 return
194         }
195
196         clearTimeoutEvent(e.id)
197 }
198
199 // The timeout event started by beforeIdle.
200 var idleTimeout *timeoutEvent
201
202 // beforeIdle gets called by the scheduler if no goroutine is awake.
203 // If we are not already handling an event, then we pause for an async event.
204 // If an event handler returned, we resume it and it will pause the execution.
205 // beforeIdle either returns the specific goroutine to schedule next or
206 // indicates with otherReady that some goroutine became ready.
207 // TODO(drchase): need to understand if write barriers are really okay in this context.
208 //
209 //go:yeswritebarrierrec
210 func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) {
211         delay := int64(-1)
212         if pollUntil != 0 {
213                 // round up to prevent setTimeout being called early
214                 delay = (pollUntil-now-1)/1e6 + 1
215                 if delay > 1e9 {
216                         // An arbitrary cap on how long to wait for a timer.
217                         // 1e9 ms == ~11.5 days.
218                         delay = 1e9
219                 }
220         }
221
222         if delay > 0 && (idleTimeout == nil || idleTimeout.diff(pollUntil) > 1e6) {
223                 // If the difference is larger than 1 ms, we should reschedule the timeout.
224                 idleTimeout.clear()
225
226                 idleTimeout = &timeoutEvent{
227                         id:   scheduleTimeoutEvent(delay),
228                         time: pollUntil,
229                 }
230         }
231
232         if len(events) == 0 {
233                 // TODO: this is the line that requires the yeswritebarrierrec
234                 go handleAsyncEvent()
235                 return nil, true
236         }
237
238         e := events[len(events)-1]
239         if e.returned {
240                 return e.gp, false
241         }
242         return nil, false
243 }
244
245 func handleAsyncEvent() {
246         pause(getcallersp() - 16)
247 }
248
249 // clearIdleTimeout clears our record of the timeout started by beforeIdle.
250 func clearIdleTimeout() {
251         idleTimeout.clear()
252         idleTimeout = nil
253 }
254
255 // pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
256 func pause(newsp uintptr)
257
258 // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
259 // It returns a timer id that can be used with clearTimeoutEvent.
260 //
261 //go:wasmimport gojs runtime.scheduleTimeoutEvent
262 func scheduleTimeoutEvent(ms int64) int32
263
264 // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
265 //
266 //go:wasmimport gojs runtime.clearTimeoutEvent
267 func clearTimeoutEvent(id int32)
268
269 // handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package
270 // and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript.
271 // When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine
272 // is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript.
273 func handleEvent() {
274         e := &event{
275                 gp:       getg(),
276                 returned: false,
277         }
278         events = append(events, e)
279
280         if !eventHandler() {
281                 // If we did not handle a window event, the idle timeout was triggered, so we can clear it.
282                 clearIdleTimeout()
283         }
284
285         // wait until all goroutines are idle
286         e.returned = true
287         gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1)
288
289         events[len(events)-1] = nil
290         events = events[:len(events)-1]
291
292         // return execution to JavaScript
293         pause(getcallersp() - 16)
294 }
295
296 // eventHandler retrieves and executes handlers for pending JavaScript events.
297 // It returns true if an event was handled.
298 var eventHandler func() bool
299
300 //go:linkname setEventHandler syscall/js.setEventHandler
301 func setEventHandler(fn func() bool) {
302         eventHandler = fn
303 }