diff --git a/Makefile b/Makefile index 80c6ee9d..04f4ded5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PACKAGES ?= mountinfo mount sequential signal symlink +PACKAGES ?= appcontext mountinfo mount sequential signal symlink BINDIR ?= _build/bin CROSS ?= linux/arm linux/arm64 linux/ppc64le linux/s390x \ freebsd/amd64 openbsd/amd64 darwin/amd64 darwin/arm64 windows/amd64 diff --git a/appcontext/appcontext.go b/appcontext/appcontext.go new file mode 100644 index 00000000..6da5cf5e --- /dev/null +++ b/appcontext/appcontext.go @@ -0,0 +1,45 @@ +package appcontext + +import ( + "context" + "os" + "os/signal" + "sync" +) + +var ( + appContextCache context.Context + appContextOnce sync.Once +) + +// Context returns a static context that reacts to termination signals of the +// running process. Useful in CLI tools. +func Context() context.Context { + appContextOnce.Do(func() { + signals := make(chan os.Signal, 2048) + signal.Notify(signals, terminationSignals...) + + const exitLimit = 3 + retries := 0 + + ctx := context.Background() + for _, f := range inits { + ctx = f(ctx) + } + + ctx, cancel := context.WithCancel(ctx) + appContextCache = ctx + + go func() { + for { + <-signals + cancel() + retries++ + if retries >= exitLimit { + os.Exit(1) + } + } + }() + }) + return appContextCache +} diff --git a/appcontext/appcontext_unix.go b/appcontext/appcontext_unix.go new file mode 100644 index 00000000..366edc68 --- /dev/null +++ b/appcontext/appcontext_unix.go @@ -0,0 +1,12 @@ +//go:build !windows +// +build !windows + +package appcontext + +import ( + "os" + + "golang.org/x/sys/unix" +) + +var terminationSignals = []os.Signal{unix.SIGTERM, unix.SIGINT} diff --git a/appcontext/appcontext_windows.go b/appcontext/appcontext_windows.go new file mode 100644 index 00000000..0a8bcbe7 --- /dev/null +++ b/appcontext/appcontext_windows.go @@ -0,0 +1,7 @@ +package appcontext + +import ( + "os" +) + +var terminationSignals = []os.Signal{os.Interrupt} diff --git a/appcontext/go.mod b/appcontext/go.mod new file mode 100644 index 00000000..c72b39af --- /dev/null +++ b/appcontext/go.mod @@ -0,0 +1,5 @@ +module github.com/moby/sys/appcontext + +go 1.17 + +require golang.org/x/sys v0.11.0 diff --git a/appcontext/go.sum b/appcontext/go.sum new file mode 100644 index 00000000..f7ca90d8 --- /dev/null +++ b/appcontext/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/appcontext/register.go b/appcontext/register.go new file mode 100644 index 00000000..22f9e836 --- /dev/null +++ b/appcontext/register.go @@ -0,0 +1,14 @@ +package appcontext + +import ( + "context" +) + +type Initializer func(context.Context) context.Context + +var inits []Initializer + +// Register stores a new context initializer that runs on app context creation +func Register(f Initializer) { + inits = append(inits, f) +}