diff --git a/environment.go b/environment.go index 63e95ae..ec57b9b 100644 --- a/environment.go +++ b/environment.go @@ -42,6 +42,7 @@ type environment struct { configFile string verbose bool crash bool + crashOnError bool } // running stores whether the environment is currently running. @@ -111,7 +112,12 @@ func (this *environment) Add(ctx context.Context, actors ...Actor) error { } } err := this.initializeActors(ctx, initializable...) - if err != nil { return err } + if err != nil { + if this.flags.crashOnError { + panic(fmt.Sprint(err)) + } + return err + } for _, actor := range actors { if actor, ok := actor.(Configurable); ok { err := actor.Configure(this.conf) @@ -246,6 +252,9 @@ func (this *environment) run(actor Actor) { } } else { log.Printf("!!! [%s] stopped with error: %v", typ, stopErr) + if this.flags.crashOnError { + panic(fmt.Sprint(stopErr)) + } } }() @@ -311,11 +320,17 @@ func (this *environment) runRunnable(ctx context.Context, actor Runnable) (stopE } else { // failure log.Printf("XXX [%s] failed: %v", typ, err) + if this.flags.crashOnError { + panic(fmt.Sprint(err)) + } } // restart logic if time.Since(lastStart) < restartThreshold { log.Printf("!!! [%s] failed too soon, restarting in %v", typ, restartInterval) + if this.flags.crashOnError { + panic("failed too soon") + } timer := time.NewTimer(restartInterval) select { case <- timer.C: @@ -341,6 +356,9 @@ func (this *environment) runRunnable(ctx context.Context, actor Runnable) (stopE err := actor.Reset(ctx) if err != nil { log.Printf("XXX [%s] failed to reset", typ) + if this.flags.crashOnError { + panic("failed to reset") + } } }() if this.Verb() { log.Printf(".// [%s] reset", typ) } diff --git a/phases.go b/phases.go index e84ebf4..1f9ddd3 100644 --- a/phases.go +++ b/phases.go @@ -19,13 +19,14 @@ func (this *environment) phase10FlagParsing() bool { name: this.name, description: this.description, } - flagHelp := set.Flag('h', "help", "Display usage information and exit", nil) - flagPidFile := set.Flag('p', "pid-file", "Write the PID to the specified file", cli.ValString) - flagUser := set.Flag('u', "user", "The user:group to run as", cli.ValString) - flagLogDirectory := set.Flag('l', "log-directory", "Write logs to the specified directory", cli.ValString) - flagConfigFile := set.Flag('c', "config-file", "Use this configuration file", cli.ValString) - flagVerbose := set.Flag('v', "verbose", "Enable verbose output/logging", nil) - flagCrash := set.Flag(0, "crash", "Crash when an actor panics", nil) + flagHelp := set.Flag('h', "help", "Display usage information and exit", nil) + flagPidFile := set.Flag('p', "pid-file", "Write the PID to the specified file", cli.ValString) + flagUser := set.Flag('u', "user", "The user:group to run as", cli.ValString) + flagLogDirectory := set.Flag('l', "log-directory", "Write logs to the specified directory", cli.ValString) + flagConfigFile := set.Flag('c', "config-file", "Use this configuration file", cli.ValString) + flagVerbose := set.Flag('v', "verbose", "Enable verbose output/logging", nil) + flagCrash := set.Flag(0, "crash", "Crash when an actor panics", nil) + flagCrashOnError := set.Flag(0, "crash-on-error", "Crash when an actor experiences any error", nil) // ask actors to add flags actors, done := this.actors.RBorrow() @@ -65,6 +66,10 @@ func (this *environment) phase10FlagParsing() bool { if _, ok := flagCrash.First(); ok { this.flags.crash = true } + if _, ok := flagCrashOnError.First(); ok { + this.flags.crash = true + this.flags.crashOnError = true + } return true } @@ -259,6 +264,9 @@ func (this *environment) phase70_5Trimming() bool { }() if err := this.trimActors(this.ctx, trimmable...); err != nil { log.Println(".// (70.5) failed to trim:", err) + if this.flags.crashOnError { + panic(err) + } return false } if this.Verb() { log.Println(".// (70.5) trimmed") } @@ -274,7 +282,7 @@ func (this *environment) phase80Shutdown() bool { <- ctx.Done() if errors.Is(context.Cause(ctx), context.DeadlineExceeded) { log.Println("XXX (80) shutdown timeout expired, performing emergency halt") - if Verb() { + if Verb() || this.flags.crashOnError { dumpBuffer := make([]byte, 8192) runtime.Stack(dumpBuffer, true) log.Printf("XXX (80) stack trace of all goroutines:\n%s", dumpBuffer)