os/signal: add NotifyContext to cancel context using system signals

Fixes #37255

Change-Id: Ic0fde3498afefed6e4447f8476e4da7c1faa7145
Reviewed-on: https://go-review.googlesource.com/c/go/+/219640
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Giovanni Bajo <rasky@develer.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Henrique Vicente 2020-02-17 02:22:47 +01:00 committed by Ian Lance Taylor
parent 8248b5791c
commit b6dbaef68f
3 changed files with 284 additions and 0 deletions

View file

@ -8,6 +8,7 @@ package signal
import (
"bytes"
"context"
"flag"
"fmt"
"internal/testenv"
@ -674,3 +675,164 @@ func TestTime(t *testing.T) {
close(stop)
<-done
}
func TestNotifyContext(t *testing.T) {
c, stop := NotifyContext(context.Background(), syscall.SIGINT)
defer stop()
if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got {
t.Errorf("c.String() = %q, want %q", got, want)
}
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
select {
case <-c.Done():
if got := c.Err(); got != context.Canceled {
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
}
case <-time.After(time.Second):
t.Errorf("timed out waiting for context to be done after SIGINT")
}
}
func TestNotifyContextStop(t *testing.T) {
Ignore(syscall.SIGHUP)
if !Ignored(syscall.SIGHUP) {
t.Errorf("expected SIGHUP to be ignored when explicitly ignoring it.")
}
parent, cancelParent := context.WithCancel(context.Background())
defer cancelParent()
c, stop := NotifyContext(parent, syscall.SIGHUP)
defer stop()
// If we're being notified, then the signal should not be ignored.
if Ignored(syscall.SIGHUP) {
t.Errorf("expected SIGHUP to not be ignored.")
}
if want, got := "signal.NotifyContext(context.Background.WithCancel, [hangup])", fmt.Sprint(c); want != got {
t.Errorf("c.String() = %q, wanted %q", got, want)
}
stop()
select {
case <-c.Done():
if got := c.Err(); got != context.Canceled {
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
}
case <-time.After(time.Second):
t.Errorf("timed out waiting for context to be done after calling stop")
}
}
func TestNotifyContextCancelParent(t *testing.T) {
parent, cancelParent := context.WithCancel(context.Background())
defer cancelParent()
c, stop := NotifyContext(parent, syscall.SIGINT)
defer stop()
if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
t.Errorf("c.String() = %q, want %q", got, want)
}
cancelParent()
select {
case <-c.Done():
if got := c.Err(); got != context.Canceled {
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
}
case <-time.After(time.Second):
t.Errorf("timed out waiting for parent context to be canceled")
}
}
func TestNotifyContextPrematureCancelParent(t *testing.T) {
parent, cancelParent := context.WithCancel(context.Background())
defer cancelParent()
cancelParent() // Prematurely cancel context before calling NotifyContext.
c, stop := NotifyContext(parent, syscall.SIGINT)
defer stop()
if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
t.Errorf("c.String() = %q, want %q", got, want)
}
select {
case <-c.Done():
if got := c.Err(); got != context.Canceled {
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
}
case <-time.After(time.Second):
t.Errorf("timed out waiting for parent context to be canceled")
}
}
func TestNotifyContextSimultaneousNotifications(t *testing.T) {
c, stop := NotifyContext(context.Background(), syscall.SIGINT)
defer stop()
if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got {
t.Errorf("c.String() = %q, want %q", got, want)
}
var wg sync.WaitGroup
n := 10
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
wg.Done()
}()
}
wg.Wait()
select {
case <-c.Done():
if got := c.Err(); got != context.Canceled {
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
}
case <-time.After(time.Second):
t.Errorf("expected context to be canceled")
}
}
func TestNotifyContextSimultaneousStop(t *testing.T) {
c, stop := NotifyContext(context.Background(), syscall.SIGINT)
defer stop()
if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got {
t.Errorf("c.String() = %q, want %q", got, want)
}
var wg sync.WaitGroup
n := 10
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
stop()
wg.Done()
}()
}
wg.Wait()
select {
case <-c.Done():
if got := c.Err(); got != context.Canceled {
t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
}
case <-time.After(time.Second):
t.Errorf("expected context to be canceled")
}
}
func TestNotifyContextStringer(t *testing.T) {
parent, cancelParent := context.WithCancel(context.Background())
defer cancelParent()
c, stop := NotifyContext(parent, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
defer stop()
want := `signal.NotifyContext(context.Background.WithCancel, [hangup interrupt terminated])`
if got := fmt.Sprint(c); got != want {
t.Errorf("c.String() = %q, want %q", got, want)
}
}