2019-08-29 14:46:54 -07:00
//! Zebrad Abscissa Application
2023-06-06 23:03:42 -07:00
use std ::{ env , fmt ::Write as _ , io ::Write as _ , process , sync ::Arc } ;
2021-10-20 06:57:09 -07:00
2019-08-29 14:46:54 -07:00
use abscissa_core ::{
2022-09-07 00:39:30 -07:00
application ::{ self , AppCell } ,
2023-06-06 23:03:42 -07:00
config ::CfgCell ,
2022-06-27 17:36:36 -07:00
status_err ,
2021-10-20 06:57:09 -07:00
terminal ::{ component ::Terminal , stderr , stdout , ColorChoice } ,
2023-06-06 23:03:42 -07:00
Application , Component , Configurable , FrameworkError , Shutdown , StandardPaths , Version ,
2019-08-29 14:46:54 -07:00
} ;
2021-01-29 04:36:33 -08:00
use zebra_network ::constants ::PORT_IN_USE_ERROR ;
2021-06-03 21:42:15 -07:00
use zebra_state ::constants ::{ DATABASE_FORMAT_VERSION , LOCK_FILE_ERROR } ;
2021-01-29 04:36:33 -08:00
2023-04-28 07:13:21 -07:00
use crate ::{
2023-06-06 23:03:42 -07:00
commands ::EntryPoint ,
2023-04-28 07:13:21 -07:00
components ::{ sync ::end_of_support ::EOS_PANIC_MESSAGE_HEADER , tracing ::Tracing } ,
config ::ZebradConfig ,
} ;
2021-10-20 06:57:09 -07:00
2022-09-07 00:39:30 -07:00
/// See <https://docs.rs/abscissa_core/latest/src/abscissa_core/application/exit.rs.html#7-10>
/// Print a fatal error message and exit
fn fatal_error ( app_name : String , err : & dyn std ::error ::Error ) -> ! {
status_err! ( " {} fatal error: {} " , app_name , err ) ;
process ::exit ( 1 )
}
2019-12-20 11:20:04 -08:00
/// Application state
pub static APPLICATION : AppCell < ZebradApp > = AppCell ::new ( ) ;
2019-08-29 14:46:54 -07:00
2021-04-21 15:14:36 -07:00
/// Returns the zebrad version for this build, in SemVer 2.0 format.
///
/// Includes the git commit and the number of commits since the last version
/// tag, if available.
///
2022-05-30 13:12:11 -07:00
/// For details, see <https://semver.org/>
2021-04-21 15:14:36 -07:00
pub fn app_version ( ) -> Version {
const CARGO_PKG_VERSION : & str = env! ( " CARGO_PKG_VERSION " ) ;
2023-05-14 08:05:22 -07:00
let vergen_git_describe : Option < & str > = option_env! ( " VERGEN_GIT_DESCRIBE " ) ;
2021-04-21 15:14:36 -07:00
2023-05-14 08:05:22 -07:00
match vergen_git_describe {
// change the git describe format to the semver 2.0 format
Some ( mut vergen_git_describe ) if ! vergen_git_describe . is_empty ( ) = > {
2021-04-21 15:14:36 -07:00
// strip the leading "v", if present
2023-05-14 08:05:22 -07:00
if & vergen_git_describe [ 0 .. 1 ] = = " v " {
vergen_git_describe = & vergen_git_describe [ 1 .. ] ;
2021-04-21 15:14:36 -07:00
}
// split into tag, commit count, hash
2023-05-14 08:05:22 -07:00
let rparts : Vec < _ > = vergen_git_describe . rsplitn ( 3 , '-' ) . collect ( ) ;
2021-04-21 15:14:36 -07:00
match rparts . as_slice ( ) {
// assume it's a cargo package version or a git tag with no hash
2023-05-14 08:05:22 -07:00
[ _ ] | [ _ , _ ] = > vergen_git_describe . parse ( ) . unwrap_or_else ( | _ | {
2021-04-21 15:14:36 -07:00
panic! (
2023-05-14 08:05:22 -07:00
" VERGEN_GIT_DESCRIBE without a hash {vergen_git_describe:?} must be valid semver 2.0 "
2021-04-21 15:14:36 -07:00
)
} ) ,
2023-05-14 08:05:22 -07:00
// it's the "git describe" format, which doesn't quite match SemVer 2.0
2021-04-21 15:14:36 -07:00
[ hash , commit_count , tag ] = > {
2022-10-27 06:25:18 -07:00
let semver_fix = format! ( " {tag} + {commit_count} . {hash} " ) ;
2021-04-21 15:14:36 -07:00
semver_fix . parse ( ) . unwrap_or_else ( | _ |
2023-05-14 08:05:22 -07:00
panic! ( " Modified VERGEN_GIT_DESCRIBE {vergen_git_describe:?} -> {rparts:?} -> {semver_fix:?} must be valid. Note: CARGO_PKG_VERSION was {CARGO_PKG_VERSION:?} . " ) )
2021-04-21 15:14:36 -07:00
}
_ = > unreachable! ( " split is limited to 3 parts " ) ,
}
}
_ = > CARGO_PKG_VERSION . parse ( ) . unwrap_or_else ( | _ | {
2022-12-07 17:05:57 -08:00
panic! ( " CARGO_PKG_VERSION {CARGO_PKG_VERSION:?} must be valid semver 2.0 " )
2021-04-21 15:14:36 -07:00
} ) ,
}
}
2023-05-04 17:29:14 -07:00
/// The Zebra current release version.
pub fn release_version ( ) -> String {
app_version ( )
. to_string ( )
. split ( '+' )
. next ( )
. expect ( " always at least 1 slice " )
. to_string ( )
}
/// The User-Agent string provided by the node.
///
/// This must be a valid [BIP 14] user agent.
///
/// [BIP 14]: https://github.com/bitcoin/bips/blob/master/bip-0014.mediawiki
pub fn user_agent ( ) -> String {
let release_version = release_version ( ) ;
format! ( " /Zebra: {release_version} / " )
}
2019-08-29 14:46:54 -07:00
/// Zebrad Application
2023-06-06 23:03:42 -07:00
#[ derive(Debug, Default) ]
2019-08-29 14:46:54 -07:00
pub struct ZebradApp {
/// Application configuration.
2023-06-06 23:03:42 -07:00
config : CfgCell < ZebradConfig > ,
2019-08-29 14:46:54 -07:00
/// Application state.
state : application ::State < Self > ,
}
2020-12-01 12:13:20 -08:00
impl ZebradApp {
2020-12-03 17:05:25 -08:00
/// Are standard output and standard error both connected to ttys?
fn outputs_are_ttys ( ) -> bool {
atty ::is ( atty ::Stream ::Stdout ) & & atty ::is ( atty ::Stream ::Stderr )
}
2021-04-19 23:53:04 -07:00
/// Returns the git commit for this build, if available.
///
///
/// # Accuracy
///
/// If the user makes changes, but does not commit them, the git commit will
/// not match the compiled source code.
pub fn git_commit ( ) -> Option < & 'static str > {
2020-12-02 21:18:55 -08:00
const GIT_COMMIT_GCLOUD : Option < & str > = option_env! ( " SHORT_SHA " ) ;
2023-05-14 08:05:22 -07:00
const GIT_COMMIT_VERGEN : Option < & str > = option_env! ( " VERGEN_GIT_SHA " ) ;
2020-12-02 21:18:55 -08:00
2021-04-19 23:53:04 -07:00
GIT_COMMIT_GCLOUD . or ( GIT_COMMIT_VERGEN )
2020-12-02 21:18:55 -08:00
}
2020-12-01 12:13:20 -08:00
}
2019-08-29 14:46:54 -07:00
impl Application for ZebradApp {
/// Entrypoint command for this application.
2022-08-30 02:01:33 -07:00
type Cmd = EntryPoint ;
2019-08-29 14:46:54 -07:00
/// Application configuration.
type Cfg = ZebradConfig ;
/// Paths to resources within the application.
type Paths = StandardPaths ;
/// Accessor for application configuration.
2023-06-06 23:03:42 -07:00
fn config ( & self ) -> Arc < ZebradConfig > {
self . config . read ( )
2019-08-29 14:46:54 -07:00
}
/// Borrow the application state immutably.
fn state ( & self ) -> & application ::State < Self > {
& self . state
}
2020-07-14 23:16:07 -07:00
/// Returns the framework components used by this application.
2020-06-04 19:34:06 -07:00
fn framework_components (
& mut self ,
2023-06-06 23:03:42 -07:00
_command : & Self ::Cmd ,
2020-06-04 19:34:06 -07:00
) -> Result < Vec < Box < dyn Component < Self > > > , FrameworkError > {
2023-06-06 23:03:42 -07:00
// TODO: Open a PR in abscissa to add a TerminalBuilder for opting out
// of the `color_eyre::install` part of `Terminal::new` without
// ColorChoice::Never?
// The Tracing component uses stdout directly and will apply colors
// `if Self::outputs_are_ttys() && config.tracing.use_colors`
2020-11-30 17:09:57 -08:00
//
2023-06-06 23:03:42 -07:00
// Note: It's important to use `ColorChoice::Never` here to avoid panicking in
// `register_components()` below if `color_eyre::install()` is called
// after `color_spantrace` has been initialized.
let terminal = Terminal ::new ( ColorChoice ::Never ) ;
2020-06-04 19:34:06 -07:00
2020-08-06 10:29:31 -07:00
Ok ( vec! [ Box ::new ( terminal ) ] )
2020-06-04 19:34:06 -07:00
}
2019-08-29 14:46:54 -07:00
/// Register all components used by this application.
///
/// If you would like to add additional components to your application
/// beyond the default ones provided by the framework, this is the place
/// to do so.
2022-03-08 01:14:15 -08:00
#[ allow(clippy::print_stderr) ]
2022-06-27 23:22:07 -07:00
#[ allow(clippy::unwrap_in_result) ]
2019-08-29 14:46:54 -07:00
fn register_components ( & mut self , command : & Self ::Cmd ) -> Result < ( ) , FrameworkError > {
2020-02-14 13:38:33 -08:00
use crate ::components ::{
metrics ::MetricsEndpoint , tokio ::TokioComponent , tracing ::TracingEndpoint ,
} ;
2019-09-09 13:05:42 -07:00
let mut components = self . framework_components ( command ) ? ;
2020-08-06 10:29:31 -07:00
2020-08-06 17:22:40 -07:00
// Load config *after* framework components so that we can
// report an error to the terminal if it occurs.
2022-06-27 17:36:36 -07:00
let config = match command . config_path ( ) {
Some ( path ) = > match self . load_config ( & path ) {
Ok ( config ) = > config ,
Err ( e ) = > {
status_err! ( " Zebra could not parse the provided config file. This might mean you are using a deprecated format of the file. You can generate a valid config by running \" zebrad generate \" , and diff it against yours to examine any format inconsistencies. " ) ;
return Err ( e ) ;
}
} ,
None = > ZebradConfig ::default ( ) ,
} ;
2020-08-06 17:22:40 -07:00
let config = command . process_config ( config ) ? ;
2020-12-03 17:05:25 -08:00
let theme = if Self ::outputs_are_ttys ( ) & & config . tracing . use_color {
color_eyre ::config ::Theme ::dark ( )
} else {
color_eyre ::config ::Theme ::new ( )
} ;
2021-04-19 23:53:04 -07:00
// collect the common metadata for the issue URL and panic report,
// skipping any env vars that aren't present
2021-04-25 20:26:06 -07:00
let app_metadata = vec! [
2021-04-19 23:53:04 -07:00
// cargo or git tag + short commit
2021-04-25 20:26:06 -07:00
( " version " , app_version ( ) . to_string ( ) ) ,
// config
( " Zcash network " , config . network . network . to_string ( ) ) ,
2021-06-03 21:42:15 -07:00
// constants
( " state version " , DATABASE_FORMAT_VERSION . to_string ( ) ) ,
2023-05-14 08:05:22 -07:00
( " features " , env! ( " VERGEN_CARGO_FEATURES " ) . to_string ( ) ) ,
2021-04-25 20:26:06 -07:00
] ;
// git env vars can be skipped if there is no `.git` during the
// build, so they must all be optional
let git_metadata : & [ ( _ , Option < _ > ) ] = & [
( " branch " , option_env! ( " VERGEN_GIT_BRANCH " ) ) ,
( " git commit " , Self ::git_commit ( ) ) ,
2021-04-19 23:53:04 -07:00
(
" commit timestamp " ,
2021-04-25 20:26:06 -07:00
option_env! ( " VERGEN_GIT_COMMIT_TIMESTAMP " ) ,
2021-04-25 19:19:55 -07:00
) ,
2021-04-25 20:26:06 -07:00
] ;
// skip missing metadata
let git_metadata : Vec < ( _ , String ) > = git_metadata
. iter ( )
. filter_map ( | ( k , v ) | Some ( ( k , ( * v ) ? ) ) )
. map ( | ( k , v ) | ( * k , v . to_string ( ) ) )
. collect ( ) ;
let build_metadata : Vec < _ > = [
( " target triple " , env! ( " VERGEN_CARGO_TARGET_TRIPLE " ) ) ,
2023-05-14 08:05:22 -07:00
( " rust compiler " , env! ( " VERGEN_RUSTC_SEMVER " ) ) ,
( " rust release date " , env! ( " VERGEN_RUSTC_COMMIT_DATE " ) ) ,
( " optimization level " , env! ( " VERGEN_CARGO_OPT_LEVEL " ) ) ,
( " debug checks " , env! ( " VERGEN_CARGO_DEBUG " ) ) ,
2021-04-19 23:53:04 -07:00
]
. iter ( )
2021-04-25 20:26:06 -07:00
. map ( | ( k , v ) | ( * k , v . to_string ( ) ) )
2021-04-19 23:53:04 -07:00
. collect ( ) ;
2021-01-11 13:46:56 -08:00
2021-04-25 20:26:06 -07:00
let panic_metadata : Vec < _ > = app_metadata
. iter ( )
. chain ( git_metadata . iter ( ) )
. chain ( build_metadata . iter ( ) )
. collect ( ) ;
2021-01-11 13:46:56 -08:00
let mut builder = color_eyre ::config ::HookBuilder ::default ( ) ;
let mut metadata_section = " Metadata: " . to_string ( ) ;
for ( k , v ) in panic_metadata {
2021-04-21 15:14:36 -07:00
builder = builder . add_issue_metadata ( k , v . clone ( ) ) ;
2022-10-27 06:25:18 -07:00
write! ( & mut metadata_section , " \n {k}: {} " , & v )
2022-05-09 20:41:51 -07:00
. expect ( " unexpected failure writing to string " ) ;
2021-01-11 13:46:56 -08:00
}
builder = builder
2020-12-03 17:05:25 -08:00
. theme ( theme )
2022-09-20 14:05:37 -07:00
. panic_section ( metadata_section . clone ( ) )
2020-12-03 17:05:25 -08:00
. issue_url ( concat! ( env! ( " CARGO_PKG_REPOSITORY " ) , " /issues/new " ) )
. issue_filter ( | kind | match kind {
2021-01-29 04:36:33 -08:00
color_eyre ::ErrorKind ::NonRecoverable ( error ) = > {
let error_str = match error . downcast_ref ::< String > ( ) {
Some ( as_string ) = > as_string ,
None = > return true ,
} ;
// listener port conflicts
if PORT_IN_USE_ERROR . is_match ( error_str ) {
return false ;
}
// RocksDB lock file conflicts
if LOCK_FILE_ERROR . is_match ( error_str ) {
return false ;
}
2023-04-28 07:13:21 -07:00
// Don't ask users to report old version panics.
if error_str . to_string ( ) . contains ( EOS_PANIC_MESSAGE_HEADER ) {
return false ;
}
2021-01-29 04:36:33 -08:00
true
}
2020-12-03 17:05:25 -08:00
color_eyre ::ErrorKind ::Recoverable ( error ) = > {
2021-11-15 06:32:18 -08:00
// Type checks should be faster than string conversions.
//
// Don't ask users to create bug reports for timeouts and peer errors.
2020-12-15 14:14:42 -08:00
if error . is ::< tower ::timeout ::error ::Elapsed > ( )
| | error . is ::< tokio ::time ::error ::Elapsed > ( )
2021-11-15 06:32:18 -08:00
| | error . is ::< zebra_network ::PeerError > ( )
| | error . is ::< zebra_network ::SharedPeerError > ( )
| | error . is ::< zebra_network ::HandshakeError > ( )
2020-12-15 14:14:42 -08:00
{
return false ;
}
2023-04-13 21:36:38 -07:00
// Don't ask users to create bug reports for known timeouts, duplicate blocks,
// full disks, or updated binaries.
2020-12-15 14:14:42 -08:00
let error_str = error . to_string ( ) ;
2021-12-09 16:19:52 -08:00
! error_str . contains ( " timed out " )
& & ! error_str . contains ( " duplicate hash " )
& & ! error_str . contains ( " No space left on device " )
2023-04-13 21:36:38 -07:00
// abscissa panics like this when the running zebrad binary has been updated
& & ! error_str . contains ( " error canonicalizing application path " )
2020-12-03 17:05:25 -08:00
}
2020-12-08 15:40:04 -08:00
} ) ;
2021-01-11 13:46:56 -08:00
// This MUST happen after `Terminal::new` to ensure our preferred panic
// handler is the last one installed
2020-12-08 15:40:04 -08:00
let ( panic_hook , eyre_hook ) = builder . into_hooks ( ) ;
2022-06-27 23:22:07 -07:00
eyre_hook . install ( ) . expect ( " eyre_hook.install() error " ) ;
2020-12-08 15:40:04 -08:00
// The Sentry default config pulls in the DSN from the `SENTRY_DSN`
// environment variable.
2022-06-16 12:56:40 -07:00
#[ cfg(feature = " sentry " ) ]
2021-11-02 11:46:57 -07:00
let guard = sentry ::init ( sentry ::ClientOptions {
debug : true ,
release : Some ( app_version ( ) . to_string ( ) . into ( ) ) ,
.. Default ::default ( )
} ) ;
2020-12-08 15:40:04 -08:00
std ::panic ::set_hook ( Box ::new ( move | panic_info | {
let panic_report = panic_hook . panic_report ( panic_info ) ;
2022-10-27 06:25:18 -07:00
eprintln! ( " {panic_report} " ) ;
2020-12-08 15:40:04 -08:00
2022-06-16 12:56:40 -07:00
#[ cfg(feature = " sentry " ) ]
2020-12-08 15:40:04 -08:00
{
let event = crate ::sentry ::panic_event_from ( panic_report ) ;
sentry ::capture_event ( event ) ;
if ! guard . close ( None ) {
warn! ( " unable to flush sentry events during panic " ) ;
}
}
} ) ) ;
2020-12-03 17:05:25 -08:00
2022-07-17 15:43:29 -07:00
// Apply the configured number of threads to the thread pool.
//
// TODO:
// - set rayon panic handler to a function that takes `Box<dyn Any + Send + 'static>`,
// which forwards to sentry. If possible, use eyre's panic report for formatting.
// - do we also need to call this code in `zebra_consensus::init()`,
// when that crate is being used by itself?
rayon ::ThreadPoolBuilder ::new ( )
. num_threads ( config . sync . parallel_cpu_threads )
2022-10-27 06:25:18 -07:00
. thread_name ( | thread_index | format! ( " rayon {thread_index} " ) )
2022-07-17 15:43:29 -07:00
. build_global ( )
. expect ( " unable to initialize rayon thread pool " ) ;
2023-06-06 23:03:42 -07:00
let cfg_ref = & config ;
let default_filter = command . cmd ( ) . default_tracing_filter ( command . verbose ) ;
let is_server = command . cmd ( ) . is_server ( ) ;
2020-08-06 10:29:31 -07:00
2022-05-05 20:31:52 -07:00
// Ignore the configured tracing filter for short-lived utility commands
2021-04-14 19:40:13 -07:00
let mut tracing_config = cfg_ref . tracing . clone ( ) ;
2022-10-24 16:39:00 -07:00
let metrics_config = cfg_ref . metrics . clone ( ) ;
2020-08-06 10:29:31 -07:00
if is_server {
2020-11-30 11:59:40 -08:00
// Override the default tracing filter based on the command-line verbosity.
tracing_config . filter = tracing_config
. filter
. or_else ( | | Some ( default_filter . to_owned ( ) ) ) ;
2020-08-06 10:29:31 -07:00
} else {
2020-11-30 11:59:40 -08:00
// Don't apply the configured filter for short-lived commands.
tracing_config . filter = Some ( default_filter . to_owned ( ) ) ;
tracing_config . flamegraph = None ;
2020-07-16 00:04:42 -07:00
}
2021-04-14 19:40:13 -07:00
components . push ( Box ::new ( Tracing ::new ( tracing_config ) ? ) ) ;
2019-09-09 13:05:42 -07:00
2022-09-20 14:05:37 -07:00
// Log git metadata and platform info when zebrad starts up
if is_server {
tracing ::info! ( " Diagnostic {} " , metadata_section ) ;
2022-11-29 20:40:02 -08:00
info! ( config_path = ? command . config_path ( ) , config = ? cfg_ref , " loaded zebrad config " ) ;
2022-09-20 14:05:37 -07:00
}
2021-01-11 13:46:56 -08:00
// Activate the global span, so it's visible when we load the other
// components. Space is at a premium here, so we use an empty message,
// short commit hash, and the unique part of the network name.
2023-06-06 23:03:42 -07:00
let net = & config . network . network . to_string ( ) [ .. 4 ] ;
2021-04-19 23:53:04 -07:00
let global_span = if let Some ( git_commit ) = ZebradApp ::git_commit ( ) {
error_span! ( " " , zebrad = git_commit , net )
} else {
error_span! ( " " , net )
} ;
2021-01-11 13:46:56 -08:00
let global_guard = global_span . enter ( ) ;
// leak the global span, to make sure it stays active
std ::mem ::forget ( global_guard ) ;
2022-07-17 15:43:29 -07:00
tracing ::info! (
num_threads = rayon ::current_num_threads ( ) ,
" initialized rayon thread pool for CPU-bound tasks " ,
) ;
2021-01-11 13:46:56 -08:00
// Launch network and async endpoints only for long-running commands.
if is_server {
components . push ( Box ::new ( TokioComponent ::new ( ) ? ) ) ;
components . push ( Box ::new ( TracingEndpoint ::new ( cfg_ref ) ? ) ) ;
2022-10-24 16:39:00 -07:00
components . push ( Box ::new ( MetricsEndpoint ::new ( & metrics_config ) ? ) ) ;
2021-01-11 13:46:56 -08:00
}
2023-06-06 23:03:42 -07:00
self . state . components_mut ( ) . register ( components ) ? ;
// Fire callback to signal state in the application lifecycle
self . after_config ( config )
2019-08-29 14:46:54 -07:00
}
2020-08-05 16:35:56 -07:00
/// Load this application's configuration and initialize its components.
2022-06-27 23:22:07 -07:00
#[ allow(clippy::unwrap_in_result) ]
2020-08-05 16:35:56 -07:00
fn init ( & mut self , command : & Self ::Cmd ) -> Result < ( ) , FrameworkError > {
// Create and register components with the application.
// We do this first to calculate a proper dependency ordering before
// application configuration is processed
2023-06-06 23:03:42 -07:00
self . register_components ( command )
2020-08-05 16:35:56 -07:00
}
2019-08-29 14:46:54 -07:00
/// Post-configuration lifecycle callback.
///
/// Called regardless of whether config is loaded to indicate this is the
/// time in app lifecycle when configuration would be loaded if
/// possible.
2020-08-06 10:29:31 -07:00
fn after_config ( & mut self , config : Self ::Cfg ) -> Result < ( ) , FrameworkError > {
2019-08-29 14:46:54 -07:00
// Configure components
2023-06-06 23:03:42 -07:00
self . state . components_mut ( ) . after_config ( & config ) ? ;
self . config . set_once ( config ) ;
2020-06-04 19:34:06 -07:00
2019-08-29 14:46:54 -07:00
Ok ( ( ) )
}
2020-08-05 16:35:56 -07:00
2023-06-06 23:03:42 -07:00
fn shutdown ( & self , shutdown : Shutdown ) -> ! {
2021-10-20 06:57:09 -07:00
// Some OSes require a flush to send all output to the terminal.
// zebrad's logging uses Abscissa, so we flush its streams.
//
// TODO:
// - if this doesn't work, send an empty line as well
// - move this code to the tracing component's `before_shutdown()`
let _ = stdout ( ) . lock ( ) . flush ( ) ;
let _ = stderr ( ) . lock ( ) . flush ( ) ;
2023-06-06 23:03:42 -07:00
let shutdown_result = self . state ( ) . components ( ) . shutdown ( self , shutdown ) ;
2022-09-07 00:39:30 -07:00
2023-06-06 23:03:42 -07:00
self . state ( )
. components_mut ( )
. get_downcast_mut ::< Tracing > ( )
. map ( Tracing ::shutdown ) ;
if let Err ( e ) = shutdown_result {
let app_name = self . name ( ) . to_string ( ) ;
2022-09-07 00:39:30 -07:00
fatal_error ( app_name , & e ) ;
2020-08-05 16:35:56 -07:00
}
match shutdown {
Shutdown ::Graceful = > process ::exit ( 0 ) ,
Shutdown ::Forced = > process ::exit ( 1 ) ,
Shutdown ::Crash = > process ::exit ( 2 ) ,
}
}
2023-06-06 23:03:42 -07:00
}
2021-04-21 15:14:36 -07:00
2023-06-06 23:03:42 -07:00
/// Boot the given application, parsing subcommand and options from
/// command-line arguments, and terminating when complete.
// <https://docs.rs/abscissa_core/0.7.0/src/abscissa_core/application.rs.html#174-178>
pub fn boot ( app_cell : & 'static AppCell < ZebradApp > ) -> ! {
let args =
EntryPoint ::process_cli_args ( env ::args_os ( ) . collect ( ) ) . unwrap_or_else ( | err | err . exit ( ) ) ;
ZebradApp ::run ( app_cell , args ) ;
process ::exit ( 0 ) ;
2020-06-04 19:34:06 -07:00
}