Source File
app.go
Belonging Package
go.uber.org/fx
// Copyright (c) 2020-2021 Uber Technologies, Inc.//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.package fximport ()// DefaultTimeout is the default timeout for starting or stopping an// application. It can be configured with the [StartTimeout] and [StopTimeout]// options.const DefaultTimeout = 15 * time.Second// An Option specifies the behavior of the application.// This is the primary means by which you interface with Fx.//// Zero or more options are specified at startup with [New].// Options cannot be changed once an application has been initialized.// Options may be grouped into a single option using the [Options] function.// A group of options providing a logical unit of functionality// may use [Module] to name that functionality// and scope certain operations to within that module.type Option interface {fmt.Stringerapply(*module)}// Error registers any number of errors with the application to short-circuit// startup. If more than one error is given, the errors are combined into a// single error.//// Similar to invocations, errors are applied in order. All Provide and Invoke// options registered before or after an Error option will not be applied.func ( ...error) Option {return errorOption()}type errorOption []errorfunc ( errorOption) ( *module) {.app.err = multierr.Append(.app.err, multierr.Combine(...))}func ( errorOption) () string {return fmt.Sprintf("fx.Error(%v)", multierr.Combine(...))}// Options bundles a group of options together into a single option.//// Use Options to group together options that don't belong in a [Module].//// var loggingAndMetrics = fx.Options(// logging.Module,// metrics.Module,// fx.Invoke(func(logger *log.Logger) {// app.globalLogger = logger// }),// )func ( ...Option) Option {return optionGroup()}type optionGroup []Optionfunc ( optionGroup) ( *module) {for , := range {.apply()}}func ( optionGroup) () string {:= make([]string, len())for , := range {[] = fmt.Sprint()}return fmt.Sprintf("fx.Options(%s)", strings.Join(, ", "))}// StartTimeout changes the application's start timeout.// This controls the total time that all [OnStart] hooks have to complete.// If the timeout is exceeded, the application will fail to start.//// Defaults to [DefaultTimeout].func ( time.Duration) Option {return startTimeoutOption()}type startTimeoutOption time.Durationfunc ( startTimeoutOption) ( *module) {if .parent != nil {.app.err = fmt.Errorf("fx.StartTimeout Option should be passed to top-level App, " +"not to fx.Module")} else {.app.startTimeout = time.Duration()}}func ( startTimeoutOption) () string {return fmt.Sprintf("fx.StartTimeout(%v)", time.Duration())}// StopTimeout changes the application's stop timeout.// This controls the total time that all [OnStop] hooks have to complete.// If the timeout is exceeded, the application will exit early.//// Defaults to [DefaultTimeout].func ( time.Duration) Option {return stopTimeoutOption()}type stopTimeoutOption time.Durationfunc ( stopTimeoutOption) ( *module) {if .parent != nil {.app.err = fmt.Errorf("fx.StopTimeout Option should be passed to top-level App, " +"not to fx.Module")} else {.app.stopTimeout = time.Duration()}}func ( stopTimeoutOption) () string {return fmt.Sprintf("fx.StopTimeout(%v)", time.Duration())}// RecoverFromPanics causes panics that occur in functions given to [Provide],// [Decorate], and [Invoke] to be recovered from.// This error can be retrieved as any other error, by using (*App).Err().func () Option {return recoverFromPanicsOption{}}type recoverFromPanicsOption struct{}func ( recoverFromPanicsOption) ( *module) {if .parent != nil {.app.err = fmt.Errorf("fx.RecoverFromPanics Option should be passed to top-level " +"App, not to fx.Module")} else {.app.recoverFromPanics = true}}func ( recoverFromPanicsOption) () string {return "fx.RecoverFromPanics()"}// WithLogger specifies the [fxevent.Logger] used by Fx to log its own events// (e.g. a constructor was provided, a function was invoked, etc.).//// The argument to this is a constructor with one of the following return// types://// fxevent.Logger// (fxevent.Logger, error)//// The constructor may depend on any other types provided to the application.// For example,//// WithLogger(func(logger *zap.Logger) fxevent.Logger {// return &fxevent.ZapLogger{Logger: logger}// })//// If specified, Fx will construct the logger and log all its events to the// specified logger.//// If Fx fails to build the logger, or no logger is specified, it will fall back to// [fxevent.ConsoleLogger] configured to write to stderr.func ( interface{}) Option {return withLoggerOption{constructor: ,Stack: fxreflect.CallerStack(1, 0),}}type withLoggerOption struct {constructor interface{}Stack fxreflect.Stack}func ( withLoggerOption) ( *module) {.logConstructor = &provide{Target: .constructor,Stack: .Stack,}}func ( withLoggerOption) () string {return fmt.Sprintf("fx.WithLogger(%s)", fxreflect.FuncName(.constructor))}// Printer is the interface required by Fx's logging backend. It's implemented// by most loggers, including the one bundled with the standard library.//// Note, this will be deprecated in a future release.// Prefer to use [fxevent.Logger] instead.type Printer interface {Printf(string, ...interface{})}// Logger redirects the application's log output to the provided printer.//// Prefer to use [WithLogger] instead.func ( Printer) Option {return loggerOption{}}type loggerOption struct{ p Printer }func ( loggerOption) ( *module) {if .parent != nil {.app.err = fmt.Errorf("fx.Logger Option should be passed to top-level App, " +"not to fx.Module")} else {:= writerFromPrinter(.p).log = fxlog.DefaultLogger() // assuming np is thread-safe.}}func ( loggerOption) () string {return fmt.Sprintf("fx.Logger(%v)", .p)}// NopLogger disables the application's log output.//// Note that this makes some failures difficult to debug,// since no errors are printed to console.// Prefer to log to an in-memory buffer instead.var NopLogger = WithLogger(func() fxevent.Logger { return fxevent.NopLogger })// An App is a modular application built around dependency injection. Most// users will only need to use the New constructor and the all-in-one Run// convenience method. In more unusual cases, users may need to use the Err,// Start, Done, and Stop methods by hand instead of relying on Run.//// [New] creates and initializes an App. All applications begin with a// constructor for the Lifecycle type already registered.//// In addition to that built-in functionality, users typically pass a handful// of [Provide] options and one or more [Invoke] options. The Provide options// teach the application how to instantiate a variety of types, and the Invoke// options describe how to initialize the application.//// When created, the application immediately executes all the functions passed// via Invoke options. To supply these functions with the parameters they// need, the application looks for constructors that return the appropriate// types; if constructors for any required types are missing or any// invocations return an error, the application will fail to start (and Err// will return a descriptive error message).//// Once all the invocations (and any required constructors) have been called,// New returns and the application is ready to be started using Run or Start.// On startup, it executes any OnStart hooks registered with its Lifecycle.// OnStart hooks are executed one at a time, in order, and must all complete// within a configurable deadline (by default, 15 seconds). For details on the// order in which OnStart hooks are executed, see the documentation for the// Start method.//// At this point, the application has successfully started up. If started via// Run, it will continue operating until it receives a shutdown signal from// Done (see the [App.Done] documentation for details); if started explicitly via// Start, it will operate until the user calls Stop. On shutdown, OnStop hooks// execute one at a time, in reverse order, and must all complete within a// configurable deadline (again, 15 seconds by default).type App struct {err errorclock fxclock.Clocklifecycle *lifecycleWrappercontainer *dig.Containerroot *module// Timeouts usedstartTimeout time.DurationstopTimeout time.Duration// Decides how we react to errors when building the graph.errorHooks []ErrorHandlervalidate bool// Whether to recover from panics in Dig containerrecoverFromPanics bool// Used to signal shutdowns.receivers signalReceiversosExit func(code int) // os.Exit override; used for testing only}// provide is a single constructor provided to Fx.type provide struct {// Constructor provided to Fx. This may be an fx.Annotated.Target interface{}// Stack trace of where this provide was made.Stack fxreflect.Stack// IsSupply is true when the Target constructor was emitted by fx.Supply.IsSupply boolSupplyType reflect.Type // set only if IsSupply// Set if the type should be provided at private scope.Private bool}// invoke is a single invocation request to Fx.type invoke struct {// Function to invoke.Target interface{}// Stack trace of where this invoke was made.Stack fxreflect.Stack}// ErrorHandler handles Fx application startup errors.// Register these with [ErrorHook].// If specified, and the application fails to start up,// the failure will still cause a crash,// but you'll have a chance to log the error or take some other action.type ErrorHandler interface {HandleError(error)}// ErrorHook registers error handlers that implement error handling functions.// They are executed on invoke failures. Passing multiple ErrorHandlers appends// the new handlers to the application's existing list.func ( ...ErrorHandler) Option {return errorHookOption()}type errorHookOption []ErrorHandlerfunc ( errorHookOption) ( *module) {.app.errorHooks = append(.app.errorHooks, ...)}func ( errorHookOption) () string {:= make([]string, len())for , := range {[] = fmt.Sprint()}return fmt.Sprintf("fx.ErrorHook(%v)", strings.Join(, ", "))}type errorHandlerList []ErrorHandlerfunc ( errorHandlerList) ( error) {for , := range {.HandleError()}}// validate sets *App into validation mode without running invoked functions.func validate( bool) Option {return &validateOption{validate: ,}}type validateOption struct {validate bool}func ( validateOption) ( *module) {if .parent != nil {.app.err = fmt.Errorf("fx.validate Option should be passed to top-level App, " +"not to fx.Module")} else {.app.validate = .validate}}func ( validateOption) () string {return fmt.Sprintf("fx.validate(%v)", .validate)}// ValidateApp validates that supplied graph would run and is not missing any dependencies. This// method does not invoke actual input functions.func ( ...Option) error {= append(, validate(true)):= New(...)return .Err()}// New creates and initializes an App, immediately executing any functions// registered via [Invoke] options. See the documentation of the App struct for// details on the application's initialization, startup, and shutdown logic.func ( ...Option) *App {:= fxlog.DefaultLogger(os.Stderr):= &App{clock: fxclock.System,startTimeout: DefaultTimeout,stopTimeout: DefaultTimeout,receivers: newSignalReceivers(),}.root = &module{app: ,// We start with a logger that writes to stderr. One of the// following three things can change this://// - fx.Logger was provided to change the output stream// - fx.WithLogger was provided to change the logger// implementation// - Both, fx.Logger and fx.WithLogger were provided//// The first two cases are straightforward: we use what the// user gave us. For the last case, however, we need to fall// back to what was provided to fx.Logger if fx.WithLogger// fails.log: ,trace: []string{fxreflect.CallerStack(1, 2)[0].String()},}for , := range {.apply(.root)}// There are a few levels of wrapping on the lifecycle here. To quickly// cover them://// - lifecycleWrapper ensures that we don't unintentionally expose the// Start and Stop methods of the internal lifecycle.Lifecycle type// - lifecycleWrapper also adapts the internal lifecycle.Hook type into// the public fx.Hook type.// - appLogger ensures that the lifecycle always logs events to the// "current" logger associated with the fx.App..lifecycle = &lifecycleWrapper{lifecycle.New(appLogger{}, .clock),}:= []dig.Option{dig.DeferAcyclicVerification(),dig.DryRun(.validate),}if .recoverFromPanics {= append(, dig.RecoverFromPanics())}.container = dig.New(...).root.build(, .container)// Provide Fx types first to increase the chance a custom logger// can be successfully built in the face of unrelated DI failure.// E.g., for a custom logger that relies on the Lifecycle type.:= fxreflect.CallerStack(0, 0) // include New in the stack for default Provides.root.provide(provide{Target: func() Lifecycle { return .lifecycle },Stack: ,}).root.provide(provide{Target: .shutdowner, Stack: }).root.provide(provide{Target: .dotGraph, Stack: }).root.provideAll()// Run decorators before executing any Invokes// (including the ones inside installAllEventLoggers)..err = multierr.Append(.err, .root.decorateAll())// If you are thinking about returning here after provides: do not (just yet)!// If a custom logger was being used, we're still buffering messages.// We'll want to flush them to the logger.// custom app logger will be initialized by the root module..root.installAllEventLoggers()// This error might have come from the provide loop above. We've// already flushed to the custom logger, so we can return.if .err != nil {return}if := .root.invokeAll(); != nil {.err =if dig.CanVisualizeError() {var bytes.Bufferdig.Visualize(.container, &, dig.VisualizeError())= errorWithGraph{graph: .String(),err: ,}}errorHandlerList(.errorHooks).HandleError()}return}func ( *App) () fxevent.Logger {return .root.log}// DotGraph contains a DOT language visualization of the dependency graph in// an Fx application. It is provided in the container by default at// initialization. On failure to build the dependency graph, it is attached// to the error and if possible, colorized to highlight the root cause of the// failure.//// Note that DotGraph does not yet recognize [Decorate] and [Replace].type DotGraph stringtype errWithGraph interface {Graph() DotGraph}type errorWithGraph struct {graph stringerr error}func ( errorWithGraph) () DotGraph {return DotGraph(.graph)}func ( errorWithGraph) () string {return .err.Error()}// VisualizeError returns the visualization of the error if available.//// Note that VisualizeError does not yet recognize [Decorate] and [Replace].func ( error) (string, error) {var errWithGraphif errors.As(, &) {if := .Graph(); != "" {return string(), nil}}return "", errors.New("unable to visualize error")}// Exits the application with the given exit code.func ( *App) ( int) {:= os.Exitif .osExit != nil {= .osExit}()}// Run starts the application, blocks on the signals channel, and then// gracefully shuts the application down. It uses [DefaultTimeout] to set a// deadline for application startup and shutdown, unless the user has// configured different timeouts with the [StartTimeout] or [StopTimeout] options.// It's designed to make typical applications simple to run.// The minimal Fx application looks like this://// fx.New().Run()//// All of Run's functionality is implemented in terms of the exported// Start, Done, and Stop methods. Applications with more specialized needs// can use those methods directly instead of relying on Run.//// After the application has started,// it can be shut down by sending a signal or calling [Shutdowner.Shutdown].// On successful shutdown, whether initiated by a signal or by the user,// Run will return to the caller, allowing it to exit cleanly.// Run will exit with a non-zero status code// if startup or shutdown operations fail,// or if the [Shutdowner] supplied a non-zero exit code.func ( *App) () {// Historically, we do not os.Exit(0) even though most applications// cede control to Fx with they call app.Run. To avoid a breaking// change, never os.Exit for success.if := .run(.Wait); != 0 {.exit()}}func ( *App) ( func() <-chan ShutdownSignal) ( int) {, := .clock.WithTimeout(context.Background(), .StartTimeout())defer ()if := .Start(); != nil {return 1}:= <-().log().LogEvent(&fxevent.Stopping{Signal: .Signal})= .ExitCode, := .clock.WithTimeout(context.Background(), .StopTimeout())defer ()if := .Stop(); != nil {return 1}return}// Err returns any error encountered during New's initialization. See the// documentation of the New method for details, but typical errors include// missing constructors, circular dependencies, constructor errors, and// invocation errors.//// Most users won't need to use this method, since both Run and Start// short-circuit if initialization failed.func ( *App) () error {return .err}var (_onStartHook = "OnStart"_onStopHook = "OnStop")// Start kicks off all long-running goroutines, like network servers or// message queue consumers. It does this by interacting with the application's// Lifecycle.//// By taking a dependency on the Lifecycle type, some of the user-supplied// functions called during initialization may have registered start and stop// hooks. Because initialization calls constructors serially and in dependency// order, hooks are naturally registered in serial and dependency order too.//// Start executes all OnStart hooks registered with the application's// Lifecycle, one at a time and in order. This ensures that each constructor's// start hooks aren't executed until all its dependencies' start hooks// complete. If any of the start hooks return an error, Start short-circuits,// calls Stop, and returns the inciting error.//// Note that Start short-circuits immediately if the New constructor// encountered any errors in application initialization.func ( *App) ( context.Context) ( error) {defer func() {.log().LogEvent(&fxevent.Started{Err: })}()if .err != nil {// Some provides failed, short-circuit immediately.return .err}return withTimeout(, &withTimeoutParams{hook: _onStartHook,callback: .start,lifecycle: .lifecycle,log: .log(),})}// withRollback will execute an anonymous function with a given context.// if the anon func returns an error, rollback methods will be called and related events emittedfunc ( *App) (context.Context,func(context.Context) error,) error {if := (); != nil {.log().LogEvent(&fxevent.RollingBack{StartErr: }):= .lifecycle.Stop().log().LogEvent(&fxevent.RolledBack{Err: })if != nil {return multierr.Append(, )}return}return nil}func ( *App) ( context.Context) error {return .withRollback(, func( context.Context) error {if := .lifecycle.Start(); != nil {return}return nil})}// Stop gracefully stops the application. It executes any registered OnStop// hooks in reverse order, so that each constructor's stop hooks are called// before its dependencies' stop hooks.//// If the application didn't start cleanly, only hooks whose OnStart phase was// called are executed. However, all those hooks are executed, even if some// fail.func ( *App) ( context.Context) ( error) {defer func() {.log().LogEvent(&fxevent.Stopped{Err: })}():= func( context.Context) error {defer .receivers.Stop()return .lifecycle.Stop()}return withTimeout(, &withTimeoutParams{hook: _onStopHook,callback: ,lifecycle: .lifecycle,log: .log(),})}// Done returns a channel of signals to block on after starting the// application. Applications listen for the SIGINT and SIGTERM signals; during// development, users can send the application SIGTERM by pressing Ctrl-C in// the same terminal as the running process.//// Alternatively, a signal can be broadcast to all done channels manually by// using the Shutdown functionality (see the [Shutdowner] documentation for details).func ( *App) () <-chan os.Signal {.receivers.Start() // No-op if runningreturn .receivers.Done()}// Wait returns a channel of [ShutdownSignal] to block on after starting the// application and function, similar to [App.Done], but with a minor difference:// if the app was shut down via [Shutdowner.Shutdown],// the exit code (if provied via [ExitCode]) will be available// in the [ShutdownSignal] struct.// Otherwise, the signal that was received will be set.func ( *App) () <-chan ShutdownSignal {.receivers.Start() // No-op if runningreturn .receivers.Wait()}// StartTimeout returns the configured startup timeout.// This defaults to [DefaultTimeout], and can be changed with the// [StartTimeout] option.func ( *App) () time.Duration {return .startTimeout}// StopTimeout returns the configured shutdown timeout.// This defaults to [DefaultTimeout], and can be changed with the// [StopTimeout] option.func ( *App) () time.Duration {return .stopTimeout}func ( *App) () (DotGraph, error) {var bytes.Buffer:= dig.Visualize(.container, &)return DotGraph(.String()),}type withTimeoutParams struct {log fxevent.Loggerhook stringcallback func(context.Context) errorlifecycle *lifecycleWrapper}// errHookCallbackExited is returned when a hook callback does not finish executingvar errHookCallbackExited = errors.New("goroutine exited without returning")func withTimeout( context.Context, *withTimeoutParams) error {:= make(chan error, 1)go func() {// If runtime.Goexit() is called from within the callback// then nothing is written to the chan.// However the defer will still be called, so we can write to the chan,// to avoid hanging until the timeout is reached.:= falsedefer func() {if ! {<- errHookCallbackExited}}()<- .callback()= true}()var errorselect {case <-.Done():= .Err()case = <-:// If the context finished at the same time as the callback// prefer the context error.// This eliminates non-determinism in select-case selection.if .Err() != nil {= .Err()}}return}// appLogger logs events to the given Fx app's "current" logger.//// Use this with lifecycle, for example, to ensure that events always go to the// correct logger.type appLogger struct{ app *App }func ( appLogger) ( fxevent.Event) {.app.log().LogEvent()}
![]() |
The pages are generated with Golds v0.8.2. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |