-
Notifications
You must be signed in to change notification settings - Fork 17.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sync: yield to the waiter when unlocking a starving mutex
When we have already assigned the semaphore ticket to a specific waiter, we want to get the waiter running as fast as possible since no other G waiting on the semaphore can acquire it optimistically. The net effect is that, when a sync.Mutex is contended, the code in the critical section guarded by the Mutex gets a priority boost. Fixes #33747 The original work was done in CL 200577 by Carlo Alberto Ferraris. The change was reverted in CL 205817 because it broke the linux-arm64-packet and solaris-amd64-oraclerel builders. Change-Id: I76d79b1d63fd206ed1c57fe6900cb7ae9e4d46cb Reviewed-on: https://go-review.googlesource.com/c/go/+/206180 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
- Loading branch information
Showing
5 changed files
with
142 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright 2019 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package runtime_test | ||
|
||
import ( | ||
. "runtime" | ||
"sync/atomic" | ||
"testing" | ||
) | ||
|
||
// TestSemaHandoff checks that when semrelease+handoff is | ||
// requested, the G that releases the semaphore yields its | ||
// P directly to the first waiter in line. | ||
// See issue 33747 for discussion. | ||
func TestSemaHandoff(t *testing.T) { | ||
const iter = 10000 | ||
ok := 0 | ||
for i := 0; i < iter; i++ { | ||
if testSemaHandoff() { | ||
ok++ | ||
} | ||
} | ||
// As long as two thirds of handoffs are direct, we | ||
// consider the test successful. The scheduler is | ||
// nondeterministic, so this test checks that we get the | ||
// desired outcome in a significant majority of cases. | ||
// The actual ratio of direct handoffs is much higher | ||
// (>90%) but we use a lower threshold to minimize the | ||
// chances that unrelated changes in the runtime will | ||
// cause the test to fail or become flaky. | ||
if ok < iter*2/3 { | ||
t.Fatal("direct handoff < 2/3:", ok, iter) | ||
} | ||
} | ||
|
||
func TestSemaHandoff1(t *testing.T) { | ||
if GOMAXPROCS(-1) <= 1 { | ||
t.Skip("GOMAXPROCS <= 1") | ||
} | ||
defer GOMAXPROCS(GOMAXPROCS(-1)) | ||
GOMAXPROCS(1) | ||
TestSemaHandoff(t) | ||
} | ||
|
||
func TestSemaHandoff2(t *testing.T) { | ||
if GOMAXPROCS(-1) <= 2 { | ||
t.Skip("GOMAXPROCS <= 2") | ||
} | ||
defer GOMAXPROCS(GOMAXPROCS(-1)) | ||
GOMAXPROCS(2) | ||
TestSemaHandoff(t) | ||
} | ||
|
||
func testSemaHandoff() bool { | ||
var sema, res uint32 | ||
done := make(chan struct{}) | ||
|
||
// We're testing that the current goroutine is able to yield its time slice | ||
// to another goroutine. Stop the current goroutine from migrating to | ||
// another CPU where it can win the race (and appear to have not yielded) by | ||
// keeping the CPUs slightly busy. | ||
for i := 0; i < GOMAXPROCS(-1); i++ { | ||
go func() { | ||
for { | ||
select { | ||
case <-done: | ||
return | ||
default: | ||
} | ||
Gosched() | ||
} | ||
}() | ||
} | ||
|
||
go func() { | ||
Semacquire(&sema) | ||
atomic.CompareAndSwapUint32(&res, 0, 1) | ||
|
||
Semrelease1(&sema, true, 0) | ||
close(done) | ||
}() | ||
for SemNwait(&sema) == 0 { | ||
Gosched() // wait for goroutine to block in Semacquire | ||
} | ||
|
||
// The crux of the test: we release the semaphore with handoff | ||
// and immediately perform a CAS both here and in the waiter; we | ||
// want the CAS in the waiter to execute first. | ||
Semrelease1(&sema, true, 0) | ||
atomic.CompareAndSwapUint32(&res, 0, 2) | ||
|
||
<-done // wait for goroutines to finish to avoid data races | ||
|
||
return res == 1 // did the waiter run first? | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters