diff --git a/zebra-state/src/service/finalized_state.rs b/zebra-state/src/service/finalized_state.rs index 93e254139..b1bdb9942 100644 --- a/zebra-state/src/service/finalized_state.rs +++ b/zebra-state/src/service/finalized_state.rs @@ -5,7 +5,14 @@ mod disk_format; #[cfg(test)] mod tests; -use std::{borrow::Borrow, collections::HashMap, convert::TryInto, path::Path, sync::Arc}; +use std::{ + borrow::Borrow, + collections::HashMap, + convert::TryInto, + io::{stderr, stdout, Write}, + path::Path, + sync::Arc, +}; use zebra_chain::{ amount::NonNegative, @@ -126,8 +133,8 @@ impl FinalizedState { // So we want to drop it before we exit. tracing::info!("closing cached state"); std::mem::drop(new_state); - tracing::info!("exiting Zebra"); - std::process::exit(0); + + Self::exit_process(); } } @@ -467,20 +474,36 @@ impl FinalizedState { // We'd like to drop the database here, because that closes the // column families and the database. But Rust's ownership rules - // make that difficult, so we just flush instead. + // make that difficult, so we just flush and delete ephemeral data instead. // TODO: remove these extra logs once bugs like #2905 are fixed self.db.flush().expect("flush is successful"); tracing::info!("flushed database to disk"); self.delete_ephemeral(); - tracing::info!("exiting Zebra"); - std::process::exit(0); + + Self::exit_process(); } result.map_err(Into::into) } + /// Exit the host process. + /// + /// Designed for debugging and tests. + fn exit_process() -> ! { + tracing::info!("exiting Zebra"); + + // Some OSes require a flush to send all output to the terminal. + // Zebra's logging doesn't depend on `tokio`, so we flush the stdlib sync streams. + // + // TODO: if this doesn't work, send an empty line as well. + let _ = stdout().lock().flush(); + let _ = stderr().lock().flush(); + + std::process::exit(0); + } + /// Commit a finalized block to the state. /// /// It's the caller's responsibility to ensure that blocks are committed in diff --git a/zebrad/src/application.rs b/zebrad/src/application.rs index 3543595ea..474465658 100644 --- a/zebrad/src/application.rs +++ b/zebrad/src/application.rs @@ -1,20 +1,19 @@ //! Zebrad Abscissa Application -use crate::{commands::ZebradCmd, components::tracing::Tracing, config::ZebradConfig}; +use std::{io::Write, process}; + use abscissa_core::{ - application::{self, AppCell}, - config, - config::Configurable, - terminal::component::Terminal, - terminal::ColorChoice, + application::{self, fatal_error, AppCell}, + config::{self, Configurable}, + terminal::{component::Terminal, stderr, stdout, ColorChoice}, Application, Component, EntryPoint, FrameworkError, Shutdown, StandardPaths, Version, }; -use application::fatal_error; -use std::process; use zebra_network::constants::PORT_IN_USE_ERROR; use zebra_state::constants::{DATABASE_FORMAT_VERSION, LOCK_FILE_ERROR}; +use crate::{commands::ZebradCmd, components::tracing::Tracing, config::ZebradConfig}; + /// Application state pub static APPLICATION: AppCell = AppCell::new(); @@ -411,6 +410,15 @@ impl Application for ZebradApp { } fn shutdown(&mut self, shutdown: Shutdown) -> ! { + // 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(); + if let Err(e) = self.state().components.shutdown(self, shutdown) { fatal_error(self, &e) }