aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/select.go
diff options
context:
space:
mode:
authorAustin Clements <austin@google.com>2016-02-26 10:50:54 -0500
committerAustin Clements <austin@google.com>2016-03-16 20:13:10 +0000
commit8fb182d0203c90ca04a04d83d37a24960012a3cc (patch)
tree0b8f799ed762b94a4173dad046c68c91a10baf60 /src/runtime/select.go
parent005140a77e535fa614fbdaa3c6c5d4c7f69f7a91 (diff)
downloadgo-8fb182d0203c90ca04a04d83d37a24960012a3cc.tar.gz
go-8fb182d0203c90ca04a04d83d37a24960012a3cc.zip
runtime: never pass stack pointers to gopark
gopark calls the unlock function after setting the G to _Gwaiting. This means it's generally unsafe to access the G's stack from the unlock function because the G may start running on another P. Once we start shrinking stacks concurrently, a stack shrink could also move the stack the moment after it enters _Gwaiting and before the unlock function is called. Document this restriction and fix the two places where we currently violate it. This is unlikely to be a problem in practice for these two places right now, but they're already skating on thin ice. For example, the following sequence could in principle cause corruption, deadlock, or a panic in the select code: On M1/P1: 1. G1 selects on channels A and B. 2. selectgoImpl calls gopark. 3. gopark puts G1 in _Gwaiting. 4. gopark calls selparkcommit. 5. selparkcommit releases the lock on channel A. On M2/P2: 6. G2 sends to channel A. 7. The send puts G1 in _Grunnable and puts it on P2's run queue. 8. The scheduler runs, selects G1, puts it in _Grunning, and resumes G1. 9. On G1, the sellock immediately following the gopark gets called. 10. sellock grows and moves the stack. On M1/P1: 11. selparkcommit continues to scan the lock order for the next channel to unlock, but it's now reading from a freed (and possibly reused) stack. This shouldn't happen in practice because step 10 isn't the first call to sellock, so the stack should already be big enough. However, once we start shrinking stacks concurrently, this reasoning won't work any more. For #12967. Change-Id: I3660c5be37e5be9f87433cb8141bdfdf37fadc4c Reviewed-on: https://go-review.googlesource.com/20038 Reviewed-by: Rick Hudson <rlh@golang.org> Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/runtime/select.go')
-rw-r--r--src/runtime/select.go30
1 files changed, 22 insertions, 8 deletions
diff --git a/src/runtime/select.go b/src/runtime/select.go
index 444427ccb7..c80c833b15 100644
--- a/src/runtime/select.go
+++ b/src/runtime/select.go
@@ -196,13 +196,27 @@ func selunlock(scases []scase, lockorder []uint16) {
}
}
-func selparkcommit(gp *g, usel unsafe.Pointer) bool {
- sel := (*hselect)(usel)
- scaseslice := slice{unsafe.Pointer(&sel.scase), int(sel.ncase), int(sel.ncase)}
- scases := *(*[]scase)(unsafe.Pointer(&scaseslice))
- lockslice := slice{unsafe.Pointer(sel.lockorder), int(sel.ncase), int(sel.ncase)}
- lockorder := *(*[]uint16)(unsafe.Pointer(&lockslice))
- selunlock(scases, lockorder)
+func selparkcommit(gp *g, _ unsafe.Pointer) bool {
+ // This must not access gp's stack (see gopark). In
+ // particular, it must not access the *hselect. That's okay,
+ // because by the time this is called, gp.waiting has all
+ // channels in lock order.
+ var lastc *hchan
+ for sg := gp.waiting; sg != nil; sg = sg.waitlink {
+ if sg.c != lastc && lastc != nil {
+ // As soon as we unlock the channel, fields in
+ // any sudog with that channel may change,
+ // including c and waitlink. Since multiple
+ // sudogs may have the same channel, we unlock
+ // only after we've passed the last instance
+ // of a channel.
+ unlock(&lastc.lock)
+ }
+ lastc = sg.c
+ }
+ if lastc != nil {
+ unlock(&lastc.lock)
+ }
return true
}
@@ -406,7 +420,7 @@ loop:
// wait for someone to wake us up
gp.param = nil
- gopark(selparkcommit, unsafe.Pointer(sel), "select", traceEvGoBlockSelect, 2)
+ gopark(selparkcommit, nil, "select", traceEvGoBlockSelect, 2)
// someone woke us up
sellock(scases, lockorder)