Initial commit
This commit is contained in:
parent
9e2108838a
commit
57e6f63124
@ -7,43 +7,23 @@ import "log"
|
|||||||
import "time"
|
import "time"
|
||||||
import "sync"
|
import "sync"
|
||||||
import "errors"
|
import "errors"
|
||||||
|
import "context"
|
||||||
|
|
||||||
type routine struct {
|
// Func is a routine created from a run function.
|
||||||
run, shutdown func () error
|
type Func func (context.Context) error
|
||||||
}
|
|
||||||
|
|
||||||
func (routine routine) Run () error {
|
// Run runs the routine.
|
||||||
if routine.run == nil {
|
func (fun Func) Run (ctx context.Context) error {
|
||||||
return nil
|
return fun(ctx)
|
||||||
} else {
|
|
||||||
return routine.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (routine routine) Shutdown () error {
|
|
||||||
if routine.shutdown == nil {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return routine.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// From creates a routine from a separate run and shutdown function.
|
|
||||||
func From (run, shutdown func () error) Routine {
|
|
||||||
return routine {
|
|
||||||
run: run,
|
|
||||||
shutdown: shutdown,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine is an object that can be run and stopped.
|
// Routine is an object that can be run and stopped.
|
||||||
type Routine interface {
|
type Routine interface {
|
||||||
// Run is a long-running function that does not return until it is
|
// Run is a long-running function that does not return until it is
|
||||||
// finished. An error is returned if the routine exited due to an error.
|
// finished, or its context is cancelled. If the context is cancelled,
|
||||||
Run () error
|
// the function must perform necessary cleanup/shutdown operations and
|
||||||
|
// exit. An error is returned if the routine exited due to an error.
|
||||||
// Shutdown stops Run.
|
Run (context.Context) error
|
||||||
Shutdown () error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager is a system capable of managing multiple routines, and restarting
|
// Manager is a system capable of managing multiple routines, and restarting
|
||||||
@ -63,99 +43,82 @@ type Manager struct {
|
|||||||
// logging altogether, this can be set to io.Discard.
|
// logging altogether, this can be set to io.Discard.
|
||||||
Logger io.Writer
|
Logger io.Writer
|
||||||
|
|
||||||
stoppingMutex sync.Mutex
|
ctx context.Context
|
||||||
stopping bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run spawns all routines in the Routines slice. If a routine exits with an
|
// Run spawns all routines in the Routines slice. If a routine exits with an
|
||||||
// error and it was running for longer than RestartDeadline, it is restarted.
|
// error and it was running for longer than RestartDeadline, it is restarted.
|
||||||
// Run returns only when all routines have exited.
|
// Run returns only when all routines have exited.
|
||||||
func (manager *Manager) Run () error {
|
func (this *Manager) Run (ctx context.Context) error {
|
||||||
var waitGroup sync.WaitGroup
|
ctx, done := context.WithCancel(ctx)
|
||||||
|
this.ctx = ctx
|
||||||
|
|
||||||
for _, routine := range manager.Routines {
|
var waitGroup sync.WaitGroup
|
||||||
|
for _, routine := range this.Routines {
|
||||||
if routine != nil {
|
if routine != nil {
|
||||||
waitGroup.Add(1)
|
waitGroup.Add(1)
|
||||||
go manager.runRoutine(routine, &waitGroup)
|
go this.runRoutine(routine, &waitGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waitGroup.Wait()
|
waitGroup.Wait()
|
||||||
|
|
||||||
|
done()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown shuts down all routines in the manager.
|
|
||||||
func (manager *Manager) Shutdown () (err error) {
|
|
||||||
manager.stoppingMutex.Lock()
|
|
||||||
manager.stopping = true
|
|
||||||
manager.stoppingMutex.Unlock()
|
|
||||||
|
|
||||||
for _, routine := range manager.Routines {
|
|
||||||
routineErr := routine.Shutdown()
|
|
||||||
if routineErr != nil {
|
|
||||||
err = routineErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append adds one or more routines to the Routines slice. This has no effect if
|
// Append adds one or more routines to the Routines slice. This has no effect if
|
||||||
// the manager is already running.
|
// the manager is already running.
|
||||||
func (manager *Manager) Append (routines ...Routine) {
|
func (this *Manager) Append (routines ...Routine) {
|
||||||
manager.Routines = append(manager.Routines, routines...)
|
this.Routines = append(this.Routines, routines...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *Manager) log (message ...any) {
|
func (this *Manager) log (message ...any) {
|
||||||
if manager.Logger == nil {
|
if this.Logger == nil {
|
||||||
log.Println(message...)
|
log.Println(message...)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(manager.Logger, message...)
|
fmt.Fprintln(this.Logger, message...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *Manager) runRoutine (routine Routine, group *sync.WaitGroup) {
|
func (this *Manager) runRoutine (routine Routine, group *sync.WaitGroup) {
|
||||||
defer group.Done()
|
defer group.Done()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
lastStart := time.Now()
|
lastStart := time.Now()
|
||||||
err := panicWrap(routine.Run)
|
err := panicWrap(routine.Run, this.ctx)
|
||||||
|
|
||||||
stopping := false
|
if ctxErr := this.ctx.Err(); ctxErr != nil {
|
||||||
manager.stoppingMutex.Lock()
|
|
||||||
stopping = manager.stopping
|
|
||||||
manager.stoppingMutex.Unlock()
|
|
||||||
if stopping {
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
manager.log("(i) stopped routine")
|
this.log("(i) stopped routine")
|
||||||
} else {
|
} else {
|
||||||
manager.log("!!! stopped routine, with error:", err)
|
this.log("!!! stopped routine, with error:", err)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
manager.log("(i) routine exited")
|
this.log("(i) routine exited")
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
manager.log("XXX routine failed:", err)
|
this.log("XXX routine failed:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Since(lastStart) < manager.RestartDeadline {
|
if time.Since(lastStart) < this.RestartDeadline {
|
||||||
manager.log("!!! not restarting routine, failed too soon")
|
this.log("!!! not restarting routine, failed too soon")
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
manager.log("(i) routine is being restarted")
|
this.log("(i) routine is being restarted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func panicWrap (f func () error) (err error) {
|
func panicWrap (f func (context.Context) error, ctx context.Context) (err error) {
|
||||||
defer func () {
|
defer func () {
|
||||||
if pan := recover(); pan != nil {
|
if pan := recover(); pan != nil {
|
||||||
err = errors.New(fmt.Sprint(pan))
|
err = errors.New(fmt.Sprint(pan))
|
||||||
}
|
}
|
||||||
} ()
|
} ()
|
||||||
|
|
||||||
err = f()
|
err = f(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user