-
Notifications
You must be signed in to change notification settings - Fork 0
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 contented, the code in the critical section guarded by the Mutex gets a priority boost. Fixes golang#33747 Change-Id: I9967f0f763c25504010651bdd7f944ee0189cd45
- Loading branch information
Showing
5 changed files
with
131 additions
and
3 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,80 @@ | ||
// 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{}) | ||
|
||
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