-
Notifications
You must be signed in to change notification settings - Fork 17.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an AfterFunc function, which registers a function to run after a context has been canceled. Add support for contexts that implement an AfterFunc method, which can be used to avoid the need to start a new goroutine watching the Done channel when propagating cancellation signals. Fixes #57928 Change-Id: If0b2cdcc4332961276a1ff57311338e74916259c Reviewed-on: https://go-review.googlesource.com/c/go/+/482695 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Damien Neil <dneil@google.com> Reviewed-by: Sameer Ajmani <sameer@golang.org>
- Loading branch information
Showing
6 changed files
with
533 additions
and
44 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pkg context, func AfterFunc(Context, func()) func() bool #57928 |
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,141 @@ | ||
// Copyright 2023 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 context_test | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"testing" | ||
"time" | ||
) | ||
|
||
// afterFuncContext is a context that's not one of the types | ||
// defined in context.go, that supports registering AfterFuncs. | ||
type afterFuncContext struct { | ||
mu sync.Mutex | ||
afterFuncs map[*struct{}]func() | ||
done chan struct{} | ||
err error | ||
} | ||
|
||
func newAfterFuncContext() context.Context { | ||
return &afterFuncContext{} | ||
} | ||
|
||
func (c *afterFuncContext) Deadline() (time.Time, bool) { | ||
return time.Time{}, false | ||
} | ||
|
||
func (c *afterFuncContext) Done() <-chan struct{} { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
if c.done == nil { | ||
c.done = make(chan struct{}) | ||
} | ||
return c.done | ||
} | ||
|
||
func (c *afterFuncContext) Err() error { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
return c.err | ||
} | ||
|
||
func (c *afterFuncContext) Value(key any) any { | ||
return nil | ||
} | ||
|
||
func (c *afterFuncContext) AfterFunc(f func()) func() bool { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
k := &struct{}{} | ||
if c.afterFuncs == nil { | ||
c.afterFuncs = make(map[*struct{}]func()) | ||
} | ||
c.afterFuncs[k] = f | ||
return func() bool { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
_, ok := c.afterFuncs[k] | ||
delete(c.afterFuncs, k) | ||
return ok | ||
} | ||
} | ||
|
||
func (c *afterFuncContext) cancel(err error) { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
if c.err != nil { | ||
return | ||
} | ||
c.err = err | ||
for _, f := range c.afterFuncs { | ||
go f() | ||
} | ||
c.afterFuncs = nil | ||
} | ||
|
||
func TestCustomContextAfterFuncCancel(t *testing.T) { | ||
ctx0 := &afterFuncContext{} | ||
ctx1, cancel := context.WithCancel(ctx0) | ||
defer cancel() | ||
ctx0.cancel(context.Canceled) | ||
<-ctx1.Done() | ||
} | ||
|
||
func TestCustomContextAfterFuncTimeout(t *testing.T) { | ||
ctx0 := &afterFuncContext{} | ||
ctx1, cancel := context.WithTimeout(ctx0, veryLongDuration) | ||
defer cancel() | ||
ctx0.cancel(context.Canceled) | ||
<-ctx1.Done() | ||
} | ||
|
||
func TestCustomContextAfterFuncAfterFunc(t *testing.T) { | ||
ctx0 := &afterFuncContext{} | ||
donec := make(chan struct{}) | ||
stop := context.AfterFunc(ctx0, func() { | ||
close(donec) | ||
}) | ||
defer stop() | ||
ctx0.cancel(context.Canceled) | ||
<-donec | ||
} | ||
|
||
func TestCustomContextAfterFuncUnregisterCancel(t *testing.T) { | ||
ctx0 := &afterFuncContext{} | ||
_, cancel := context.WithCancel(ctx0) | ||
if got, want := len(ctx0.afterFuncs), 1; got != want { | ||
t.Errorf("after WithCancel(ctx0): ctx0 has %v afterFuncs, want %v", got, want) | ||
} | ||
cancel() | ||
if got, want := len(ctx0.afterFuncs), 0; got != want { | ||
t.Errorf("after canceling WithCancel(ctx0): ctx0 has %v afterFuncs, want %v", got, want) | ||
} | ||
} | ||
|
||
func TestCustomContextAfterFuncUnregisterTimeout(t *testing.T) { | ||
ctx0 := &afterFuncContext{} | ||
_, cancel := context.WithTimeout(ctx0, veryLongDuration) | ||
if got, want := len(ctx0.afterFuncs), 1; got != want { | ||
t.Errorf("after WithTimeout(ctx0, d): ctx0 has %v afterFuncs, want %v", got, want) | ||
} | ||
cancel() | ||
if got, want := len(ctx0.afterFuncs), 0; got != want { | ||
t.Errorf("after canceling WithTimeout(ctx0, d): ctx0 has %v afterFuncs, want %v", got, want) | ||
} | ||
} | ||
|
||
func TestCustomContextAfterFuncUnregisterAfterFunc(t *testing.T) { | ||
ctx0 := &afterFuncContext{} | ||
stop := context.AfterFunc(ctx0, func() {}) | ||
if got, want := len(ctx0.afterFuncs), 1; got != want { | ||
t.Errorf("after AfterFunc(ctx0, f): ctx0 has %v afterFuncs, want %v", got, want) | ||
} | ||
stop() | ||
if got, want := len(ctx0.afterFuncs), 0; got != want { | ||
t.Errorf("after stopping AfterFunc(ctx0, f): ctx0 has %v afterFuncs, want %v", got, want) | ||
} | ||
} |
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
Oops, something went wrong.