-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
context: cancelCtx exclusive lock causes extreme contention #42564
Comments
Thank you for raising this issue. Can you please provide more information, a sample program which demonstrates this, or if that is not possible, the pprof trace you used to identify the lock contention. Thank you |
If you have 64 hardware threads, |
As a workaround, you can guard slow Err() call with a non-blocking Done() read: done := ctx.Done()
for something {
select {
case <-done:
return ctx.Err()
default:
}
} |
FWIW, this just showed up in some of our profiles recently. It was only 1% of our overall CPU time, but I expected 0. Here's a benchmark: func BenchmarkContextCancelDone(b *testing.B) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
select {
case <-ctx.Done():
default:
}
}
})
} Profile:
If you cache the
Then it's much faster:
Looks like this is due to @josharian's 986768d ( @josharian elsewhere suggested we could switch /cc @crawshaw |
And fortunately #39351 ( I'll mark this as Go 1.17, with the assumption that that'll work out. |
(It'd probably need to |
Change https://golang.org/cl/288193 mentions this issue: |
This doesn't need compare and swap; we already have a mutex available for other reasons to guard writes. I took a first stab in CL 288193, but apparently I screwed something up. Will aim to fix next week... |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
Passed a single context with cancellation to a bunch of goroutines.
These goroutines had a cold-path compute task, interlaced with calls to
context.Err()
to detect cancellation.The loop looks something like:
What did you expect to see?
A bit of a slowdown from the context check maybe?
What did you see instead?
Slightly over 50% of the CPU time was spent in
runtime.findrunnable
. ThecancelContext
struct uses async.Mutex
, and due to extreme lock contention (64 CPU threads spamming it), this was triggeringlockSlow
. From poking at pprof, it appears that about 86% of CPU time was spent in functions related to this lock acquire.I was able to work around this by adding a counter and checking it less frequently. However, I do not think that this is an intended performance degradation path. Theoretically this could be made more efficient with
sync/atomic
, although I think async.RWMutex
would still be more than sufficient.The text was updated successfully, but these errors were encountered: