Skip to content

Commit 0452f94

Browse files
neelanceRichard Musiol
authored and
Richard Musiol
committed
runtime: fix race condition between timer and event handler
This change fixes a race condition between beforeIdle waking up the innermost event handler and a timer causing a different goroutine to wake up at the exact same moment. This messes up the wasm event handling and leads to memory corruption. The solution is to make beforeIdle return the goroutine that must run next and have findrunnable pick this goroutine without considering timers again. Fixes #38093 Fixes #38574 Change-Id: Iffbe99411d25c2730953d1c8b0741fd892f8e540 Reviewed-on: https://go-review.googlesource.com/c/go/+/230178 Run-TryBot: Richard Musiol <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]>
1 parent 7dbbb5b commit 0452f94

File tree

5 files changed

+70
-12
lines changed

5 files changed

+70
-12
lines changed

src/runtime/lock_futex.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ func notetsleepg(n *note, ns int64) bool {
238238
return ok
239239
}
240240

241-
func beforeIdle(int64) bool {
242-
return false
241+
func beforeIdle(int64) (*g, bool) {
242+
return nil, false
243243
}
244244

245245
func checkTimeouts() {}

src/runtime/lock_js.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ var idleID int32
173173
// beforeIdle gets called by the scheduler if no goroutine is awake.
174174
// If we are not already handling an event, then we pause for an async event.
175175
// If an event handler returned, we resume it and it will pause the execution.
176-
func beforeIdle(delay int64) bool {
176+
// beforeIdle either returns the specific goroutine to schedule next or
177+
// indicates with otherReady that some goroutine became ready.
178+
func beforeIdle(delay int64) (gp *g, otherReady bool) {
177179
if delay > 0 {
178180
clearIdleID()
179181
if delay < 1e6 {
@@ -190,15 +192,14 @@ func beforeIdle(delay int64) bool {
190192

191193
if len(events) == 0 {
192194
go handleAsyncEvent()
193-
return true
195+
return nil, true
194196
}
195197

196198
e := events[len(events)-1]
197199
if e.returned {
198-
goready(e.gp, 1)
199-
return true
200+
return e.gp, false
200201
}
201-
return false
202+
return nil, false
202203
}
203204

204205
func handleAsyncEvent() {

src/runtime/lock_sema.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ func notetsleepg(n *note, ns int64) bool {
297297
return ok
298298
}
299299

300-
func beforeIdle(int64) bool {
301-
return false
300+
func beforeIdle(int64) (*g, bool) {
301+
return nil, false
302302
}
303303

304304
func checkTimeouts() {}

src/runtime/proc.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -2286,9 +2286,17 @@ stop:
22862286

22872287
// wasm only:
22882288
// If a callback returned and no other goroutine is awake,
2289-
// then pause execution until a callback was triggered.
2290-
if beforeIdle(delta) {
2291-
// At least one goroutine got woken.
2289+
// then wake event handler goroutine which pauses execution
2290+
// until a callback was triggered.
2291+
gp, otherReady := beforeIdle(delta)
2292+
if gp != nil {
2293+
casgstatus(gp, _Gwaiting, _Grunnable)
2294+
if trace.enabled {
2295+
traceGoUnpark(gp, 0)
2296+
}
2297+
return gp, false
2298+
}
2299+
if otherReady {
22922300
goto top
22932301
}
22942302

test/fixedbugs/issue38093.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// +build js
2+
// run
3+
4+
// Copyright 2020 The Go Authors. All rights reserved.
5+
// Use of this source code is governed by a BSD-style
6+
// license that can be found in the LICENSE file.
7+
8+
// Test race condition between timers and wasm calls that led to memory corruption.
9+
10+
package main
11+
12+
import (
13+
"os"
14+
"syscall/js"
15+
"time"
16+
)
17+
18+
func main() {
19+
ch1 := make(chan struct{})
20+
21+
go func() {
22+
for {
23+
time.Sleep(5 * time.Millisecond)
24+
ch1 <- struct{}{}
25+
}
26+
}()
27+
go func() {
28+
for {
29+
time.Sleep(8 * time.Millisecond)
30+
ch1 <- struct{}{}
31+
}
32+
}()
33+
go func() {
34+
time.Sleep(2 * time.Second)
35+
os.Exit(0)
36+
}()
37+
38+
for range ch1 {
39+
ch2 := make(chan struct{}, 1)
40+
f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
41+
ch2 <- struct{}{}
42+
return nil
43+
})
44+
defer f.Release()
45+
fn := js.Global().Get("Function").New("cb", "cb();")
46+
fn.Invoke(f)
47+
<-ch2
48+
}
49+
}

0 commit comments

Comments
 (0)