Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
10be6883eb | |||
af58f960c5 |
117
exiter/exitCodes.go
Normal file
117
exiter/exitCodes.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package exiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"gitlab.com/CRThaze/cworthy/exit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExitCode represents an unsigned 16 bit integer used to define exit statuses.
|
||||||
|
type ExitCode exit.ExitT
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExitOK ExitCode = ExitCode(exit.EX_OK) // 0: successful termination
|
||||||
|
ExitError ExitCode = ExitCode(1) // 1: generic error
|
||||||
|
ExitUsage ExitCode = ExitCode(exit.EX_USAGE) // 64: command line usage error
|
||||||
|
ExitDataErr ExitCode = ExitCode(exit.EX_DATAERR) // 65: data format error
|
||||||
|
ExitNoInput ExitCode = ExitCode(exit.EX_NOINPUT) // 66: cannot open input
|
||||||
|
ExitNoUser ExitCode = ExitCode(exit.EX_NOUSER) // 67: addressee unknown
|
||||||
|
ExitNoHost ExitCode = ExitCode(exit.EX_NOHOST) // 68: host name unknown
|
||||||
|
ExitUnavailable ExitCode = ExitCode(exit.EX_UNAVAILABLE) // 69: service unavailable
|
||||||
|
ExitSoftware ExitCode = ExitCode(exit.EX_SOFTWARE) // 70: internal software error
|
||||||
|
ExitOSErr ExitCode = ExitCode(exit.EX_OSERR) // 71: system error (e.g., can't fork)
|
||||||
|
ExitOSFile ExitCode = ExitCode(exit.EX_OSFILE) // 72: critical OS file missing
|
||||||
|
ExitCantCreate ExitCode = ExitCode(exit.EX_CANTCREAT) // 73: can't create (user) output file
|
||||||
|
ExitIOErr ExitCode = ExitCode(exit.EX_IOERR) // 74: input/output error
|
||||||
|
ExitTempFail ExitCode = ExitCode(exit.EX_TEMPFAIL) // 75: temp failure; user is invited to retry
|
||||||
|
ExitProtocol ExitCode = ExitCode(exit.EX_PROTOCOL) // 76: remote error in protocol
|
||||||
|
ExitNoPerm ExitCode = ExitCode(exit.EX_NOPERM) // 77: permission denied
|
||||||
|
ExitConfig ExitCode = ExitCode(exit.EX_CONFIG) // 78: configuration error
|
||||||
|
ExitAbort ExitCode = ExitCode(exit.EX_ABORT) // 134: process was aborted
|
||||||
|
)
|
||||||
|
|
||||||
|
var exitCodeDescs [^ExitCode(0)]string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
exitCodeDescs = exit.GetAllExitCodeDescriptions()
|
||||||
|
exitCodeDescs[ExitError] = "generic error"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desc returns the description string for a given ExitCode.
|
||||||
|
func (e ExitCode) Desc() string {
|
||||||
|
return exitCodeDescs[e]
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterNewExitCode reserves an ExitCode and stores its corresponding
|
||||||
|
// description string for recall.
|
||||||
|
// It returns an error if the given integer is already in use or outside the
|
||||||
|
// usable range. And also if the description string is empty.
|
||||||
|
func RegisterNewExitCode(num int, desc string) (ExitCode, error) {
|
||||||
|
if num < 0 || num > int(^ExitCode(0)) {
|
||||||
|
return 0, errors.New("New exit code out of range")
|
||||||
|
}
|
||||||
|
if num == 0 || num == int(exit.EX_ABORT) || (num >= int(exit.EX__BASE) && num <= int(exit.EX__MAX)) {
|
||||||
|
return 0, errors.New("New exit code not user reservable")
|
||||||
|
}
|
||||||
|
if exitCodeDescs[num] != "" {
|
||||||
|
return 0, errors.New("New exit code already defined")
|
||||||
|
}
|
||||||
|
if desc == "" {
|
||||||
|
return 0, errors.New("New exit code description cannot be blank")
|
||||||
|
}
|
||||||
|
exitCodeDescs[num] = desc
|
||||||
|
return ExitCode(num), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterNextExitCode find the next available ExitCode number and associates the
|
||||||
|
// given description string with it.
|
||||||
|
// It returns an error if there are no more available numbers to reserve.
|
||||||
|
func RegisterNextExitCode(desc string) (ExitCode, error) {
|
||||||
|
nextUsable := func(n int) int {
|
||||||
|
n++
|
||||||
|
for n == int(exit.EX_ABORT) || (n >= int(exit.EX__BASE) && n <= int(exit.EX__MAX)) {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
for i := 1; i <= int(^ExitCode(0)); i = nextUsable(i) {
|
||||||
|
if exitCodeDescs[i] == "" {
|
||||||
|
exitCodeDescs[i] = desc
|
||||||
|
return ExitCode(i), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.New("Available exit codes exhausted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapCodes(codes []string, offset int, m map[ExitCode]string) {
|
||||||
|
for i, desc := range codes {
|
||||||
|
if desc != "" {
|
||||||
|
m[ExitCode(i+offset)] = desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserCodeMap returns a map of ExitCodes and their associated description strings
|
||||||
|
// with in the User-Definable range.
|
||||||
|
func UserCodeMap() (m map[ExitCode]string) {
|
||||||
|
m = map[ExitCode]string{}
|
||||||
|
mapCodes(exitCodeDescs[1:exit.EX__BASE], 1, m)
|
||||||
|
mapCodes(exitCodeDescs[exit.EX__MAX+1:exit.EX_ABORT], int(exit.EX__MAX)+1, m)
|
||||||
|
mapCodes(exitCodeDescs[exit.EX_ABORT+1:], int(exit.EX_ABORT)+1, m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullCodeMap returns a map of all ExitCodes and their associated description
|
||||||
|
// strings.
|
||||||
|
func FullCodeMap() (m map[ExitCode]string) {
|
||||||
|
m = map[ExitCode]string{}
|
||||||
|
mapCodes(exitCodeDescs[:], 0, m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllExitCodeDescriptions returns an array of all possible ExitCode description
|
||||||
|
// strings (where the index is the exit code). Any unreserved ExitCodes are also
|
||||||
|
// included with empty string descriptions.
|
||||||
|
func GetAllExitCodeDescriptions() [^ExitCode(0)]string {
|
||||||
|
return exitCodeDescs
|
||||||
|
}
|
373
exiter/exiter.go
Normal file
373
exiter/exiter.go
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
package exiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"gitlab.com/CRThaze/sugar/sigar"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
exitSignalGroupName = "exitSignalGroup"
|
||||||
|
abortSignalGroupName = "abortSignalGroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExitTaskFn (`func()`) is a closure represening something that should be
|
||||||
|
// executed right before exiting.
|
||||||
|
type ExitTaskFn sigar.TaskFn
|
||||||
|
|
||||||
|
// WriteFn is a format function that will write output somewhere.
|
||||||
|
// This could be a custom function or simply a logger function pointer.
|
||||||
|
type WriteFn func(format string, a ...interface{})
|
||||||
|
|
||||||
|
// ExiterConfig contains options for the creation of an Exiter.
|
||||||
|
type ExiterConfig struct {
|
||||||
|
ParentCtx context.Context
|
||||||
|
ExitSignals []os.Signal
|
||||||
|
SuppressAllExitMsg bool
|
||||||
|
ShowOKExitMsg bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exiter is a tool for cleanly and consistently exiting the program.
|
||||||
|
// Instead of creating the struct directly, use the NewExiter() or
|
||||||
|
// NewExiterWithConfig() factories.
|
||||||
|
type Exiter struct {
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
sigar *sigar.SignalCatcher
|
||||||
|
preCancelTasks *list.List
|
||||||
|
postCancelTasks *list.List
|
||||||
|
usagePrintFunc ExitTaskFn
|
||||||
|
exitInfoMsgWriteFunc WriteFn
|
||||||
|
exitErrMsgWriteFunc WriteFn
|
||||||
|
suppressAllExitMsg bool
|
||||||
|
showOKExitMsg bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultExiterConfig is the ExiterConfig used when calling the
|
||||||
|
// NewExiter() factory.
|
||||||
|
var DefaultExiterConfig = ExiterConfig{
|
||||||
|
ParentCtx: context.Background(),
|
||||||
|
ExitSignals: []os.Signal{
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExiterWithConfig is an Exiter factory that allows you to
|
||||||
|
// customize the configuration.
|
||||||
|
// Be sure to call (Exiter).ListenForSignals() once created to handle
|
||||||
|
// all exits properly.
|
||||||
|
func NewExiterWithConfig(config ExiterConfig) (e *Exiter) {
|
||||||
|
e = &Exiter{
|
||||||
|
suppressAllExitMsg: config.SuppressAllExitMsg,
|
||||||
|
showOKExitMsg: config.ShowOKExitMsg,
|
||||||
|
}
|
||||||
|
e.ctx, e.cancel = context.WithCancel(config.ParentCtx)
|
||||||
|
e.sigar = sigar.NewSignalCatcher(
|
||||||
|
sigar.SignalGroups{
|
||||||
|
exitSignalGroupName: config.ExitSignals,
|
||||||
|
abortSignalGroupName: []os.Signal{syscall.SIGABRT},
|
||||||
|
},
|
||||||
|
e.ctx,
|
||||||
|
)
|
||||||
|
e.sigar.RegisterTask(exitSignalGroupName, func() {
|
||||||
|
e.ExitWithOK()
|
||||||
|
})
|
||||||
|
e.sigar.RegisterTask(abortSignalGroupName, func() {
|
||||||
|
e.Exit(ExitAbort)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExiter is an Exiter factory that uses the DefaultExiterConfig.
|
||||||
|
// Be sure to call (Exiter).ListenForSignals() once created to handle
|
||||||
|
// all exits properly.
|
||||||
|
func NewExiter() *Exiter { return NewExiterWithConfig(DefaultExiterConfig) }
|
||||||
|
|
||||||
|
// SpawnExiter is a convenience function that calls both NewExiter()
|
||||||
|
// and (Exiter).ListenForSignals().
|
||||||
|
func SpawnExiter() (e *Exiter) {
|
||||||
|
e = NewExiter()
|
||||||
|
e.ListenForSignals()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpawnExiterWithConfig is a convenience function that calls both
|
||||||
|
// NewExiterWithConfig() and (Exiter).ListenForSignals().
|
||||||
|
func SpawnExiterWithConfig(config ExiterConfig) (e *Exiter) {
|
||||||
|
e = NewExiterWithConfig(config)
|
||||||
|
e.ListenForSignals()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenForSignals spawns a goroutine to catch signals so that the
|
||||||
|
// Exiter can gracefully shutdown the program.
|
||||||
|
func (e *Exiter) ListenForSignals() {
|
||||||
|
e.sigar.Listen()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterPreTask adds a task to the queue that will be executed
|
||||||
|
// prior to unblocking goroutines blocked at (Exiter).Wait().
|
||||||
|
func (e *Exiter) RegisterPreTask(fn ExitTaskFn) {
|
||||||
|
e.preCancelTasks.PushBack(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterPostTask adds a task to the queue that will be executed
|
||||||
|
// after unblocking goroutines blocked at (Exiter).Wait().
|
||||||
|
func (e *Exiter) RegisterPostTask(fn ExitTaskFn) {
|
||||||
|
e.postCancelTasks.PushBack(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUsageFunc tells the Exiter what function to call to print the
|
||||||
|
// program's help/usage information.
|
||||||
|
func (e *Exiter) SetUsageFunc(usage ExitTaskFn) {
|
||||||
|
e.usagePrintFunc = usage
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitOnPanic is a convenience function that is meant to be defered
|
||||||
|
// as a means to gracefully exit in the event of a panic.
|
||||||
|
func (e *Exiter) ExitOnPanic() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
e.exitErrMsgWriteFunc("Paniced: %v", r)
|
||||||
|
e.ExitWithSoftwareErr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exiter) exit(code ExitCode) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
e.exitErrMsgWriteFunc(
|
||||||
|
"Paniced attempting to cleanup tasks (with exit code: %d): %v", code, r,
|
||||||
|
)
|
||||||
|
os.Exit(int(ExitSoftware))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for x := e.preCancelTasks.Front(); x != nil; x = x.Next() {
|
||||||
|
x.Value.(ExitTaskFn)()
|
||||||
|
}
|
||||||
|
e.cancel()
|
||||||
|
for x := e.postCancelTasks.Front(); x != nil; x = x.Next() {
|
||||||
|
x.Value.(ExitTaskFn)()
|
||||||
|
}
|
||||||
|
os.Exit(int(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exiter) writeExitMsg(code ExitCode, fn WriteFn, err ...error) {
|
||||||
|
if !e.suppressAllExitMsg && fn != nil {
|
||||||
|
if code == ExitOK && !e.showOKExitMsg {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(err) != 0 && err[0] != nil {
|
||||||
|
fn("%s: %v", ExitSoftware.Desc(), err[0])
|
||||||
|
} else {
|
||||||
|
fn("%s", ExitSoftware.Desc())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithOK calls (Exiter).Exit() with ExitOK (0).
|
||||||
|
//
|
||||||
|
// It is recommended to use this for all normal exit conditions.
|
||||||
|
func (e *Exiter) ExitWithOK() {
|
||||||
|
e.Exit(ExitOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithUsageOK prints the help/usage and calls (Exiter).Exit() with
|
||||||
|
// ExitOK (0). If no UsageFunc was set, it will not print anything.
|
||||||
|
//
|
||||||
|
// It is recommended to use this when responding to help arguments.
|
||||||
|
func (e *Exiter) ExitWithUsageOK() {
|
||||||
|
e.RegisterPostTask(e.usagePrintFunc)
|
||||||
|
e.Exit(ExitOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithError calls (Exiter).Exit() with ExitError (1).
|
||||||
|
//
|
||||||
|
// It is recommended to use this when no other exit code makes sense.
|
||||||
|
func (e *Exiter) ExitWithError(err ...error) {
|
||||||
|
e.Exit(ExitError, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithUsageErr prints the help/usage and calls (Exiter).Exit() with
|
||||||
|
// ExitUsage (64). If no UsageFunc was set, it will not print anything.
|
||||||
|
//
|
||||||
|
// It is recommended to use this when bad arguments are passed.
|
||||||
|
func (e *Exiter) ExitWithUsageErr(err ...error) {
|
||||||
|
e.Exit(ExitUsage, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithDataErr calls (Exiter).Exit() with ExitDataErr (65).
|
||||||
|
//
|
||||||
|
// It is recommended to use this when data provided through any means
|
||||||
|
// is invalid but NOT when an argument fails basic validation
|
||||||
|
// (though schema errors in complex arguments could be appropriate UNLESS
|
||||||
|
// they are configuartion settings, in which case you should use
|
||||||
|
// (Exiter).ExitWithConfigErr()).
|
||||||
|
func (e *Exiter) ExitWithDataErr(err ...error) {
|
||||||
|
e.Exit(ExitDataErr, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithNoInputErr calls (Exiter).Exit() with ExitNoInput (66).
|
||||||
|
//
|
||||||
|
// It is recommended to use this only when an input channel (such as
|
||||||
|
// a pipe, os.STDIN, or virtual device) cannot be opened or read when
|
||||||
|
// needed.
|
||||||
|
// Permissions errors should use (Exiter).ExitWithNoPermErr() instead.
|
||||||
|
func (e *Exiter) ExitWithNoInputErr(err ...error) {
|
||||||
|
e.Exit(ExitNoInput, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithNoUserErr calls (Exiter).Exit() with ExitNoUser (67).
|
||||||
|
//
|
||||||
|
// It is recommended to use this only when attempting to address data to
|
||||||
|
// a local or network user, and no suitable match can be found. Mostly
|
||||||
|
// useful for multi-user machines or intranet communication not using
|
||||||
|
// a protocol that has its own suitable statuses for this.
|
||||||
|
func (e *Exiter) ExitWithNoUserErr(err ...error) {
|
||||||
|
e.Exit(ExitNoUser, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithNoHostErr calls (Exiter).Exit() with ExitNoHost (68).
|
||||||
|
//
|
||||||
|
// It is recommended to use this when a provided hostname cannot be resolved.
|
||||||
|
func (e *Exiter) ExitWithNoHostErr(err ...error) {
|
||||||
|
e.Exit(ExitNoHost, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithUnavailableErr calls (Exiter).Exit() with ExitUnavailable (69) (nice).
|
||||||
|
//
|
||||||
|
// It is recommended to use this for client programs which cannot communicate
|
||||||
|
// with another process or remote service.
|
||||||
|
func (e *Exiter) ExitWithUnavailableErr(err ...error) {
|
||||||
|
e.Exit(ExitUnavailable, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithSoftwareErr calls (Exiter).Exit() with ExitSoftware (70).
|
||||||
|
//
|
||||||
|
// It is recommended to use this as a more meaningful alternative to
|
||||||
|
// ExitWithError() or for more graceful exits in the event of a Panic.
|
||||||
|
// For the later case consider deferring (Exiter).ExitOnPanic() which
|
||||||
|
// will call this function in that event.
|
||||||
|
func (e *Exiter) ExitWithSoftwareErr(err ...error) {
|
||||||
|
e.Exit(ExitSoftware, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithOSErr calls (Exiter).Exit() with ExitOSErr (71).
|
||||||
|
//
|
||||||
|
// It is recommended to use this only when unable to continue due to
|
||||||
|
// encountering a generalized system error.
|
||||||
|
func (e *Exiter) ExitWithOSErr(err ...error) {
|
||||||
|
e.Exit(ExitOSErr, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithOSFile calls (Exiter).Exit() with ExitOSFile (72).
|
||||||
|
//
|
||||||
|
// It is recommended to use this only when unable to find a file or directory
|
||||||
|
// not owned by this or any othe program but is part of the general
|
||||||
|
// system configuration. Such as a standard FHS directory, or a device file
|
||||||
|
// in /dev.
|
||||||
|
func (e *Exiter) ExitWithOSFile(err ...error) {
|
||||||
|
e.Exit(ExitOSFile, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithCantCreateErr calls (Exiter).Exit() with ExitCantCreate (73).
|
||||||
|
//
|
||||||
|
// It is recommended to use this only when the creation of a user output
|
||||||
|
// file is impossible for any reason and the program is unable to continue.
|
||||||
|
func (e *Exiter) ExitWithCantCreateErr(err ...error) {
|
||||||
|
e.Exit(ExitCantCreate, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithIOErr calls (Exiter).Exit() with ExitIOErr (74).
|
||||||
|
//
|
||||||
|
// It is recommended to use this only when unable to interact with I/O
|
||||||
|
// hardware, failure to open or write to an output stream, or when
|
||||||
|
// encountering an interuption from an already open input stream
|
||||||
|
// (otherwise use (Exiter).ExitWithNoInputErr()).
|
||||||
|
// Permissions errors should use (Exiter).ExitWithNoPermErr() instead.
|
||||||
|
func (e *Exiter) ExitWithIOErr(err ...error) {
|
||||||
|
e.Exit(ExitIOErr, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithTempFailErr calls (Exiter).Exit() with ExitTempFail (75).
|
||||||
|
//
|
||||||
|
// It is recommended to use this when the program chooses (by design) to
|
||||||
|
// terminate instead of retrying some operation that is expected to have
|
||||||
|
// failed merely intermittently.
|
||||||
|
func (e *Exiter) ExitWithTempFailErr(err ...error) {
|
||||||
|
e.Exit(ExitTempFail, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithProtocolErr calls (Exiter).Exit() with ExitProtocol (76).
|
||||||
|
//
|
||||||
|
// It is recommended to use this only when some communication with a
|
||||||
|
// process or network location fails due to a protocol violation from
|
||||||
|
// the other communication participant. And example would be receiving
|
||||||
|
// a response that is not inline with the protocol (like a SYN or ACK
|
||||||
|
// in response to the same instead of a SYN/ACK when performing a TCP
|
||||||
|
// handshake).
|
||||||
|
func (e *Exiter) ExitWithProtocolErr(err ...error) {
|
||||||
|
e.Exit(ExitTempFail, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithNoPermErr calls (Exiter).Exit() with ExitNoPerm (77).
|
||||||
|
//
|
||||||
|
// It is recommended to use this whenever user or credential Permissions
|
||||||
|
// do not allow you to continue.
|
||||||
|
func (e *Exiter) ExitWithNoPermErr(err ...error) {
|
||||||
|
e.Exit(ExitNoPerm, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithConfigErr calls (Exiter).Exit() with ExitConfig (78).
|
||||||
|
//
|
||||||
|
// It is recommended to use this whenever a schema validation is encountered
|
||||||
|
// in a config file or other serialiezed configuarion source.
|
||||||
|
func (e *Exiter) ExitWithConfigErr(err ...error) {
|
||||||
|
e.Exit(ExitConfig, err...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitWithConfigErr exits with the given ExitCode, and prints the
|
||||||
|
// first error provided (if any).
|
||||||
|
func (e *Exiter) Exit(code ExitCode, err ...error) {
|
||||||
|
switch code {
|
||||||
|
case ExitOK:
|
||||||
|
e.RegisterPostTask(func() {
|
||||||
|
e.writeExitMsg(code, e.exitInfoMsgWriteFunc)
|
||||||
|
})
|
||||||
|
e.exit(code)
|
||||||
|
case ExitUsage:
|
||||||
|
e.RegisterPostTask(func() {
|
||||||
|
e.writeExitMsg(code, e.exitErrMsgWriteFunc, err...)
|
||||||
|
})
|
||||||
|
e.RegisterPostTask(e.usagePrintFunc)
|
||||||
|
e.exit(code)
|
||||||
|
default:
|
||||||
|
e.RegisterPostTask(func() {
|
||||||
|
e.writeExitMsg(code, e.exitErrMsgWriteFunc, err...)
|
||||||
|
})
|
||||||
|
e.exit(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait blocks till (Exiter).Exit() is called.
|
||||||
|
// It is recommended to call this function in a select block case in
|
||||||
|
// all goroutines to ensure they are properly terminated.
|
||||||
|
func (e *Exiter) Wait() {
|
||||||
|
<-e.ctx.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCancelableContext returns the underlying cancelable context
|
||||||
|
// used by the Exiter.
|
||||||
|
func (e *Exiter) GetCancelableContext() context.Context {
|
||||||
|
return e.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignalCatcher returns the underlying *sigar.SignalCatcher
|
||||||
|
// used by the Exiter.
|
||||||
|
func (e *Exiter) GetSignalCatcher() *sigar.SignalCatcher {
|
||||||
|
return e.sigar
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -1,3 +1,5 @@
|
|||||||
module gitlab.com/CRThaze/sugar
|
module gitlab.com/CRThaze/sugar
|
||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
|
require gitlab.com/CRThaze/cworthy v0.0.4
|
||||||
|
12
go.sum
12
go.sum
@ -0,0 +1,12 @@
|
|||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/novalagung/go-eek v1.0.1/go.mod h1:Q9MQtLRP21rO5/7UmIJUr5URe1ysI+K/wM3zYJCxPxo=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
gitlab.com/CRThaze/cworthy v0.0.4 h1:fvbM0UMdYxhHSE19GyIFuGly/hM9UT3Pw3E1urHKt1I=
|
||||||
|
gitlab.com/CRThaze/cworthy v0.0.4/go.mod h1:eaZtqJCLikd8gegTlbxaOvo8623mHhIoOySY00RfrZQ=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
87
sigar/signalHandler.go
Normal file
87
sigar/signalHandler.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package sigar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type taskMap map[string]*list.List
|
||||||
|
|
||||||
|
type SignalGroups map[string][]os.Signal
|
||||||
|
|
||||||
|
func (sg SignalGroups) initTaskMap(tm taskMap) {
|
||||||
|
for groupName := range sg {
|
||||||
|
tm[groupName] = list.New()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskFn func()
|
||||||
|
|
||||||
|
type SignalCatcher struct {
|
||||||
|
signals SignalGroups
|
||||||
|
tasks map[string]*list.List
|
||||||
|
exitCtx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSignalCatcher(sg SignalGroups, exitCtx context.Context) (sc *SignalCatcher) {
|
||||||
|
if sg == nil {
|
||||||
|
sg = SignalGroups{}
|
||||||
|
}
|
||||||
|
sc = &SignalCatcher{
|
||||||
|
signals: sg,
|
||||||
|
exitCtx: exitCtx,
|
||||||
|
tasks: taskMap{},
|
||||||
|
}
|
||||||
|
sg.initTaskMap(sc.tasks)
|
||||||
|
if exitCtx == nil {
|
||||||
|
// Create a cancelable context and discard the cancel function, as we will never use it.
|
||||||
|
sc.exitCtx, _ = context.WithCancel(context.Background())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpawnSignalCatcher(sg SignalGroups, exitCtx context.Context) (sc *SignalCatcher) {
|
||||||
|
sc = NewSignalCatcher(sg, exitCtx)
|
||||||
|
sc.Listen()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SignalCatcher) runTasks(group string) {
|
||||||
|
taskList := sc.tasks[group]
|
||||||
|
for e := taskList.Front(); e != nil; e = e.Next() {
|
||||||
|
e.Value.(TaskFn)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SignalCatcher) Listen() {
|
||||||
|
for groupName, signalSet := range sc.signals {
|
||||||
|
// Prevent loop closure by copying variables.
|
||||||
|
group := groupName
|
||||||
|
signals := signalSet
|
||||||
|
go func() {
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, signals...)
|
||||||
|
select {
|
||||||
|
case <-sigChan:
|
||||||
|
sc.runTasks(group)
|
||||||
|
case <-sc.exitCtx.Done():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SignalCatcher) RegisterTaskGroup(group string, signals []os.Signal) {
|
||||||
|
sc.signals[group] = signals
|
||||||
|
sc.tasks[group] = list.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SignalCatcher) RegisterTask(group string, fn TaskFn) error {
|
||||||
|
if _, ok := sc.tasks[group]; !ok {
|
||||||
|
return errors.New("No such task group")
|
||||||
|
}
|
||||||
|
sc.tasks[group].PushBack(fn)
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user