mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
os/signal: make NotifyContext cancel the context with a cause
This is especially useful when combined with the nesting semantics of context.Cause, and with errgroup's use of CancelCauseFunc. For example, with the following code ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() serveGroup, ctx := errgroup.WithContext(ctx) calling context.Cause(ctx) after serveGroup.Wait() will return either "interrupt signal received" (if that happens first) or the error from serveGroup. Change-Id: Ie181f5f84269f6e39defdad2d5fd8ead6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/721700 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Mark Freeman <markfreeman@google.com> Reviewed-by: Sean Liao <sean@liao.dev> Auto-Submit: Filippo Valsorda <filippo@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Commit-Queue: Junyang Shao <shaojunyang@google.com> Reviewed-by: Junyang Shao <shaojunyang@google.com>
This commit is contained in:
parent
ca37d24e0b
commit
c1b7112af8
3 changed files with 37 additions and 11 deletions
2
doc/next/6-stdlib/99-minor/os/signal/notifycontext.md
Normal file
2
doc/next/6-stdlib/99-minor/os/signal/notifycontext.md
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[NotifyContext] now cancels the returned context with [context.CancelCauseFunc]
|
||||||
|
and an error indicating which signal was received.
|
||||||
|
|
@ -272,11 +272,14 @@ func process(sig os.Signal) {
|
||||||
// the returned context. Future interrupts received will not trigger the default
|
// the returned context. Future interrupts received will not trigger the default
|
||||||
// (exit) behavior until the returned stop function is called.
|
// (exit) behavior until the returned stop function is called.
|
||||||
//
|
//
|
||||||
|
// If a signal causes the returned context to be canceled, calling
|
||||||
|
// [context.Cause] on it will return an error describing the signal.
|
||||||
|
//
|
||||||
// The stop function releases resources associated with it, so code should
|
// The stop function releases resources associated with it, so code should
|
||||||
// call stop as soon as the operations running in this Context complete and
|
// call stop as soon as the operations running in this Context complete and
|
||||||
// signals no longer need to be diverted to the context.
|
// signals no longer need to be diverted to the context.
|
||||||
func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc) {
|
func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc) {
|
||||||
ctx, cancel := context.WithCancel(parent)
|
ctx, cancel := context.WithCancelCause(parent)
|
||||||
c := &signalCtx{
|
c := &signalCtx{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
|
|
@ -287,8 +290,8 @@ func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Co
|
||||||
if ctx.Err() == nil {
|
if ctx.Err() == nil {
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-c.ch:
|
case s := <-c.ch:
|
||||||
c.cancel()
|
c.cancel(signalError(s.String() + " signal received"))
|
||||||
case <-c.Done():
|
case <-c.Done():
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -299,13 +302,13 @@ func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Co
|
||||||
type signalCtx struct {
|
type signalCtx struct {
|
||||||
context.Context
|
context.Context
|
||||||
|
|
||||||
cancel context.CancelFunc
|
cancel context.CancelCauseFunc
|
||||||
signals []os.Signal
|
signals []os.Signal
|
||||||
ch chan os.Signal
|
ch chan os.Signal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *signalCtx) stop() {
|
func (c *signalCtx) stop() {
|
||||||
c.cancel()
|
c.cancel(nil)
|
||||||
Stop(c.ch)
|
Stop(c.ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -333,3 +336,9 @@ func (c *signalCtx) String() string {
|
||||||
buf = append(buf, ')')
|
buf = append(buf, ')')
|
||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type signalError string
|
||||||
|
|
||||||
|
func (s signalError) Error() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package signal
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"internal/testenv"
|
"internal/testenv"
|
||||||
|
|
@ -723,6 +724,9 @@ func TestNotifyContextNotifications(t *testing.T) {
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
if got, want := context.Cause(ctx).Error(), "interrupt signal received"; got != want {
|
||||||
|
t.Errorf("context.Cause(ctx) = %q, want %q", got, want)
|
||||||
|
}
|
||||||
fmt.Println("received SIGINT")
|
fmt.Println("received SIGINT")
|
||||||
// Sleep to give time to simultaneous signals to reach the process.
|
// Sleep to give time to simultaneous signals to reach the process.
|
||||||
// These signals must be ignored given stop() is not called on this code.
|
// These signals must be ignored given stop() is not called on this code.
|
||||||
|
|
@ -797,11 +801,15 @@ func TestNotifyContextStop(t *testing.T) {
|
||||||
if got := c.Err(); got != context.Canceled {
|
if got := c.Err(); got != context.Canceled {
|
||||||
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
||||||
}
|
}
|
||||||
|
if got := context.Cause(c); got != context.Canceled {
|
||||||
|
t.Errorf("context.Cause(c.Err()) = %q, want %q", got, context.Canceled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotifyContextCancelParent(t *testing.T) {
|
func TestNotifyContextCancelParent(t *testing.T) {
|
||||||
parent, cancelParent := context.WithCancel(context.Background())
|
parent, cancelParent := context.WithCancelCause(context.Background())
|
||||||
defer cancelParent()
|
parentCause := errors.New("parent canceled")
|
||||||
|
defer cancelParent(parentCause)
|
||||||
c, stop := NotifyContext(parent, syscall.SIGINT)
|
c, stop := NotifyContext(parent, syscall.SIGINT)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
|
|
@ -809,18 +817,22 @@ func TestNotifyContextCancelParent(t *testing.T) {
|
||||||
t.Errorf("c.String() = %q, want %q", got, want)
|
t.Errorf("c.String() = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelParent()
|
cancelParent(parentCause)
|
||||||
<-c.Done()
|
<-c.Done()
|
||||||
if got := c.Err(); got != context.Canceled {
|
if got := c.Err(); got != context.Canceled {
|
||||||
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
||||||
}
|
}
|
||||||
|
if got := context.Cause(c); got != parentCause {
|
||||||
|
t.Errorf("context.Cause(c) = %q, want %q", got, parentCause)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotifyContextPrematureCancelParent(t *testing.T) {
|
func TestNotifyContextPrematureCancelParent(t *testing.T) {
|
||||||
parent, cancelParent := context.WithCancel(context.Background())
|
parent, cancelParent := context.WithCancelCause(context.Background())
|
||||||
defer cancelParent()
|
parentCause := errors.New("parent canceled")
|
||||||
|
defer cancelParent(parentCause)
|
||||||
|
|
||||||
cancelParent() // Prematurely cancel context before calling NotifyContext.
|
cancelParent(parentCause) // Prematurely cancel context before calling NotifyContext.
|
||||||
c, stop := NotifyContext(parent, syscall.SIGINT)
|
c, stop := NotifyContext(parent, syscall.SIGINT)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
|
|
@ -832,6 +844,9 @@ func TestNotifyContextPrematureCancelParent(t *testing.T) {
|
||||||
if got := c.Err(); got != context.Canceled {
|
if got := c.Err(); got != context.Canceled {
|
||||||
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
|
||||||
}
|
}
|
||||||
|
if got := context.Cause(c); got != parentCause {
|
||||||
|
t.Errorf("context.Cause(c) = %q, want %q", got, parentCause)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotifyContextSimultaneousStop(t *testing.T) {
|
func TestNotifyContextSimultaneousStop(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue