diff options
author | Austin Clements <austin@google.com> | 2016-02-15 17:38:06 -0500 |
---|---|---|
committer | Austin Clements <austin@google.com> | 2016-03-16 20:13:20 +0000 |
commit | 276b1777717ec5a0a02364a5aee806f8076bb60b (patch) | |
tree | 71a951c4987ff43c58cebaf4b73c9e87820dfbfd /src/runtime/chan_test.go | |
parent | d45bf7228f742e63a30eef03e5288df332906838 (diff) | |
download | go-276b1777717ec5a0a02364a5aee806f8076bb60b.tar.gz go-276b1777717ec5a0a02364a5aee806f8076bb60b.zip |
runtime: make shrinkstack concurrent-safe
Currently shinkstack is only safe during STW because it adjusts
channel-related stack pointers and moves send/receive stack slots
without synchronizing with the channel code. Make it safe to use when
the world isn't stopped by:
1) Locking all channels the G is blocked on while adjusting the sudogs
and copying the area of the stack that may contain send/receive
slots.
2) For any stack frames that may contain send/receive slot, using an
atomic CAS to adjust pointers to prevent races between adjusting a
pointer in a receive slot and a concurrent send writing to that
receive slot.
In principle, the synchronization could be finer-grained. For example,
we considered synchronizing around the sudogs, which would allow
channel operations involving other Gs to continue if the G being
shrunk was far enough down the send/receive queue. However, using the
channel lock means no additional locks are necessary in the channel
code. Furthermore, the stack shrinking code holds the channel lock for
a very short time (much less than the time required to shrink the
stack).
This does not yet make stack shrinking concurrent; it merely makes
doing so safe.
This has negligible effect on the go1 and garbage benchmarks.
For #12967.
Change-Id: Ia49df3a8a7be4b36e365aac4155a2416b94b988c
Reviewed-on: https://go-review.googlesource.com/20042
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
Diffstat (limited to 'src/runtime/chan_test.go')
-rw-r--r-- | src/runtime/chan_test.go | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/src/runtime/chan_test.go b/src/runtime/chan_test.go index 2cdfae866c..911821bea5 100644 --- a/src/runtime/chan_test.go +++ b/src/runtime/chan_test.go @@ -586,6 +586,67 @@ func TestSelectDuplicateChannel(t *testing.T) { c <- 8 // wake up B. This operation used to fail because c.recvq was corrupted (it tries to wake up an already running G instead of B) } +var selectSink interface{} + +func TestSelectStackAdjust(t *testing.T) { + // Test that channel receive slots that contain local stack + // pointers are adjusted correctly by stack shrinking. + c := make(chan *int) + d := make(chan *int) + ready := make(chan bool) + go func() { + // Temporarily grow the stack to 10K. + stackGrowthRecursive((10 << 10) / (128 * 8)) + + // We're ready to trigger GC and stack shrink. + ready <- true + + val := 42 + var cx *int + cx = &val + // Receive from d. cx won't be affected. + select { + case cx = <-c: + case <-d: + } + + // Check that pointer in cx was adjusted correctly. + if cx != &val { + t.Error("cx no longer points to val") + } else if val != 42 { + t.Error("val changed") + } else { + *cx = 43 + if val != 43 { + t.Error("changing *cx failed to change val") + } + } + ready <- true + }() + + // Let the goroutine get into the select. + <-ready + time.Sleep(10 * time.Millisecond) + + // Force concurrent GC a few times. + var before, after runtime.MemStats + runtime.ReadMemStats(&before) + for i := 0; i < 100; i++ { + selectSink = new([1 << 20]byte) + runtime.ReadMemStats(&after) + if after.NumGC-before.NumGC >= 2 { + goto done + } + } + t.Fatal("failed to trigger concurrent GC") +done: + selectSink = nil + + // Wake select. + d <- nil + <-ready +} + func BenchmarkChanNonblocking(b *testing.B) { myc := make(chan int) b.RunParallel(func(pb *testing.PB) { |