diff --git a/servers/src/common/types.rs b/servers/src/common/types.rs index ea83fecf..a428a5eb 100644 --- a/servers/src/common/types.rs +++ b/servers/src/common/types.rs @@ -19,7 +19,6 @@ use std::sync::Arc; use chrono::prelude::Utc; use rand::prelude::*; -use crate::api; use crate::chain; use crate::core::global::{ChainTypes, DEFAULT_FUTURE_TIME_LIMIT}; use crate::core::{core, libtx, pow}; @@ -28,6 +27,7 @@ use crate::p2p; use crate::pool; use crate::pool::types::DandelionConfig; use crate::store; +use crate::{api, Server}; /// Error type wrapping underlying module errors. #[derive(Debug)] @@ -405,3 +405,17 @@ impl DandelionEpoch { self.relay_peer.clone() } } + +/// Server initialization status. +pub enum ServerInitStatus { + /// Database loading. + LoadDatabase, + /// P2P server initialization. + StartSync, + /// API server initialization. + StartAPI, + /// Server instance after successful initialization. + FinishedLoading(Server), + /// Error on initialization. + ErrorLoading(Error), +} diff --git a/servers/src/grin/server.rs b/servers/src/grin/server.rs index d3510fe2..f492ed17 100644 --- a/servers/src/grin/server.rs +++ b/servers/src/grin/server.rs @@ -39,7 +39,7 @@ use crate::common::hooks::{init_chain_hooks, init_net_hooks}; use crate::common::stats::{ ChainStats, DiffBlock, DiffStats, PeerStats, ServerStateInfo, ServerStats, TxStats, }; -use crate::common::types::{Error, ServerConfig, StratumServerConfig}; +use crate::common::types::{Error, ServerConfig, ServerInitStatus, StratumServerConfig}; use crate::core::core::hash::{Hashed, ZERO_HASH}; use crate::core::ser::ProtocolVersion; use crate::core::{consensus, genesis, global, pow}; @@ -52,7 +52,6 @@ use crate::pool; use crate::util::file::get_first_line; use crate::util::{RwLock, StopState}; use futures::channel::oneshot; -use grin_util::logger::LogEntry; /// Arcified thread-safe TransactionPool with type parameters used by server components pub type ServerTxPool = Arc>>; @@ -84,20 +83,16 @@ impl Server { /// Instantiates and starts a new server. Optionally takes a callback /// for the server to send an ARC copy of itself, to allow another process /// to poll info about the server status - pub fn start( + pub fn start( config: ServerConfig, - logs_rx: Option>, - mut info_callback: F, stop_state: Option>, + server_tx: Option>, api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>), - ) -> Result<(), Error> - where - F: FnMut(Server, Option>), - { + ) -> Result { let mining_config = config.stratum_mining_config.clone(); let enable_test_miner = config.run_test_miner; let test_miner_wallet_url = config.test_miner_wallet_url.clone(); - let serv = Server::new(config, stop_state, api_chan)?; + let serv = Server::new(config, stop_state, server_tx, api_chan)?; if let Some(c) = mining_config { let enable_stratum_server = c.enable_stratum_server; @@ -118,8 +113,7 @@ impl Server { } } - info_callback(serv, logs_rx); - Ok(()) + Ok(serv) } // Exclusive (advisory) lock_file to ensure we do not run multiple @@ -151,6 +145,7 @@ impl Server { pub fn new( config: ServerConfig, stop_state: Option>, + server_tx: Option>, api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>), ) -> Result { // Obtain our lock_file or fail immediately with an error. @@ -193,6 +188,10 @@ impl Server { info!("Starting server, genesis block: {}", genesis.hash()); + if let Some(ref server_tx) = server_tx { + let _ = server_tx.send(ServerInitStatus::LoadDatabase); + } + let shared_chain = Arc::new(chain::Chain::init( config.db_root.clone(), chain_adapter.clone(), @@ -220,6 +219,10 @@ impl Server { }; debug!("Capabilities: {:?}", capabilities); + if let Some(ref server_tx) = server_tx { + let _ = server_tx.send(ServerInitStatus::StartSync); + } + let p2p_server = Arc::new(p2p::Server::new( &config.db_root, capabilities, @@ -265,6 +268,10 @@ impl Server { } })?; + if let Some(ref server_tx) = server_tx { + let _ = server_tx.send(ServerInitStatus::StartAPI); + } + info!("Starting rest apis at: {}", &config.api_http_addr); let api_secret = get_first_line(config.api_secret_path.clone()); let foreign_api_secret = get_first_line(config.foreign_api_secret_path.clone()); diff --git a/src/bin/cmd/server.rs b/src/bin/cmd/server.rs index 6eb60f9f..fbca7d47 100644 --- a/src/bin/cmd/server.rs +++ b/src/bin/cmd/server.rs @@ -28,7 +28,10 @@ use crate::tui::ui; use futures::channel::oneshot; use grin_p2p::msg::PeerAddrs; use grin_p2p::PeerAddr; +use grin_servers::common::types::ServerInitStatus; +use grin_servers::Server; use grin_util::logger::LogEntry; +use grin_util::StopState; use std::sync::mpsc; /// wrap below to allow UI to clean up on stop @@ -37,38 +40,50 @@ pub fn start_server( logs_rx: Option>, api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>), ) { - start_server_tui(config, logs_rx, api_chan); - exit(0); + exit(start_server_tui(config, logs_rx, api_chan)); } fn start_server_tui( config: servers::ServerConfig, logs_rx: Option>, api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>), -) { - // Run the UI controller.. here for now for simplicity to access - // everything it might need +) -> i32 { if config.run_tui.unwrap_or(false) { warn!("Starting GRIN in UI mode..."); - servers::Server::start( - config, - logs_rx, - |serv: servers::Server, logs_rx: Option>| { - let mut controller = ui::Controller::new(logs_rx.unwrap()).unwrap_or_else(|e| { - panic!("Error loading UI controller: {}", e); - }); - controller.run(serv); - }, - None, - api_chan, - ) - .unwrap(); + // Run the UI controller. + let (serv_tx, serv_rx) = mpsc::channel::(); + let mut controller = ui::Controller::new(logs_rx, serv_rx).unwrap_or_else(|e| { + panic!("Error loading UI controller: {}", e); + }); + let serv_tx_clone = serv_tx.clone(); + let stop_state = Arc::new(StopState::new()); + let stop_state_clone = stop_state.clone(); + thread::spawn(move || { + match Server::start( + config, + Some(stop_state_clone.clone()), + Some(serv_tx_clone.clone()), + api_chan, + ) { + Ok(s) => { + if stop_state_clone.is_stopped() { + s.stop(); + return; + } + let _ = serv_tx_clone.send(ServerInitStatus::FinishedLoading(s)); + } + Err(e) => { + let _ = serv_tx_clone.send(ServerInitStatus::ErrorLoading(e)); + } + } + }); + let exit_code = controller.run(); + stop_state.stop(); + exit_code } else { warn!("Starting GRIN w/o UI..."); - servers::Server::start( - config, - logs_rx, - |serv: servers::Server, _: Option>| { + match Server::start(config, None, None, api_chan) { + Ok(s) => { let running = Arc::new(AtomicBool::new(true)); let r = running.clone(); ctrlc::set_handler(move || { @@ -79,12 +94,14 @@ fn start_server_tui( thread::sleep(Duration::from_secs(1)); } warn!("Received SIGINT (Ctrl+C) or SIGTERM (kill)."); - serv.stop(); - }, - None, - api_chan, - ) - .unwrap(); + s.stop(); + 0 + } + Err(e) => { + error!("Error starting GRIN: {:?}", e); + 1 + } + } } } diff --git a/src/bin/tui/ui.rs b/src/bin/tui/ui.rs index 3021ebdf..e28876dc 100644 --- a/src/bin/tui/ui.rs +++ b/src/bin/tui/ui.rs @@ -15,6 +15,12 @@ //! Basic TUI to better output the overall system status and status //! of various subsystems +use super::constants::MAIN_MENU; +use crate::built_info; +use crate::servers::Server; +use crate::tui::constants::{ROOT_STACK, VIEW_BASIC_STATUS, VIEW_MINING, VIEW_PEER_SYNC}; +use crate::tui::types::{TUIStatusListener, UIMessage}; +use crate::tui::{logs, menu, mining, peers, status, version}; use chrono::prelude::Utc; use cursive::direction::Orientation; use cursive::theme::BaseColor::{Black, Blue, Cyan, White}; @@ -29,24 +35,20 @@ use cursive::views::{ CircularFocus, Dialog, LinearLayout, Panel, SelectView, StackView, TextView, ViewRef, }; use cursive::{CursiveRunnable, CursiveRunner}; -use std::sync::mpsc; -use std::{thread, time}; - -use super::constants::MAIN_MENU; -use crate::built_info; -use crate::servers::Server; -use crate::tui::constants::{ROOT_STACK, VIEW_BASIC_STATUS, VIEW_MINING, VIEW_PEER_SYNC}; -use crate::tui::types::{TUIStatusListener, UIMessage}; -use crate::tui::{logs, menu, mining, peers, status, version}; use grin_core::global; +use grin_servers::common::types::{Error, ServerInitStatus}; use grin_util::logger::LogEntry; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc}; +use std::{thread, time}; pub struct UI { cursive: CursiveRunner, ui_rx: mpsc::Receiver, ui_tx: mpsc::Sender, controller_tx: mpsc::Sender, - logs_rx: mpsc::Receiver, + logs_rx: Option>, + show_dialog: Arc, } fn modify_theme(theme: &mut Theme) { @@ -65,7 +67,7 @@ impl UI { /// Create a new UI pub fn new( controller_tx: mpsc::Sender, - logs_rx: mpsc::Receiver, + logs_rx: Option>, ) -> UI { let (ui_tx, ui_rx) = mpsc::channel::(); @@ -75,6 +77,7 @@ impl UI { ui_rx, controller_tx, logs_rx, + show_dialog: Arc::new(AtomicBool::new(false)), }; // Create UI objects, etc @@ -102,7 +105,7 @@ impl UI { built_info::PKG_VERSION, global::get_chain_type() ), - Color::Dark(BaseColor::Green), + Dark(BaseColor::Green), )); let main_layer = LinearLayout::new(Orientation::Vertical) @@ -117,17 +120,21 @@ impl UI { let mut theme = grin_ui.cursive.current_theme().clone(); modify_theme(&mut theme); grin_ui.cursive.set_theme(theme); + grin_ui.cursive.add_fullscreen_layer(main_layer); // Configure a callback (shutdown, for the first test) let controller_tx_clone = grin_ui.controller_tx.clone(); + let show_dialog_clone = grin_ui.show_dialog.clone(); grin_ui.cursive.add_global_callback('q', move |c| { + if show_dialog_clone.load(Ordering::Relaxed) { + c.pop_layer(); + } let content = StyledString::styled("Shutting down...", Color::Light(BaseColor::Yellow)); c.add_layer(CircularFocus::new(Dialog::around(TextView::new(content))).wrap_tab()); - controller_tx_clone - .send(ControllerMessage::Shutdown) - .unwrap(); + let _ = controller_tx_clone.send(ControllerMessage::Shutdown); }); + grin_ui.cursive.set_fps(3); grin_ui } @@ -139,8 +146,10 @@ impl UI { return false; } - while let Some(message) = self.logs_rx.try_iter().next() { - logs::TUILogsView::update(&mut self.cursive, message); + if let Some(logs_rx) = &self.logs_rx { + while let Some(message) = logs_rx.try_iter().next() { + logs::TUILogsView::update(&mut self.cursive, message); + } } // Process any pending UI messages @@ -174,6 +183,8 @@ impl UI { pub struct Controller { rx: mpsc::Receiver, ui: UI, + serv_rx: mpsc::Receiver, + server: Option, } pub enum ControllerMessage { @@ -182,39 +193,99 @@ pub enum ControllerMessage { impl Controller { /// Create a new controller - pub fn new(logs_rx: mpsc::Receiver) -> Result { + pub fn new( + logs_rx: Option>, + serv_rx: mpsc::Receiver, + ) -> Result { let (tx, rx) = mpsc::channel::(); Ok(Controller { rx, ui: UI::new(tx, logs_rx), + serv_rx, + server: None, }) } + /// Server initialization status. + pub fn init_status(&mut self, text: &str, pop: bool) { + if pop { + self.ui.cursive.pop_layer(); + } + let content = StyledString::styled(text, Color::Light(BaseColor::Green)); + self.ui + .cursive + .add_layer(CircularFocus::new(Dialog::around(TextView::new(content))).wrap_tab()); + self.ui.show_dialog.store(true, Ordering::Relaxed); + } + + /// Server initialization error. + pub fn init_error(&mut self, e: Error) { + let content = StyledString::styled(format!("{:?}", e), Color::Light(BaseColor::Red)); + self.ui.cursive.add_layer( + CircularFocus::new(Dialog::around(TextView::new(content)).button("Exit", |s| { + s.quit(); + })) + .wrap_tab(), + ); + self.ui.show_dialog.store(true, Ordering::Relaxed); + } + + /// Server UI after initialization. + pub fn server(&mut self, server: &Server) { + if let Ok(stats) = server.get_server_stats() { + self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap(); + } + } + /// Run the controller - pub fn run(&mut self, server: Server) { + pub fn run(&mut self) -> i32 { + self.init_status("Starting server...", false); + let stat_update_interval = 1; let mut next_stat_update = Utc::now().timestamp() + stat_update_interval; let delay = time::Duration::from_millis(50); + let mut exit_code = 0; while self.ui.step() { if let Some(message) = self.rx.try_iter().next() { match message { ControllerMessage::Shutdown => { warn!("Shutdown in progress, please wait"); self.ui.stop(); - server.stop(); - return; + if let Some(s) = self.server.take() { + s.stop(); + } + return exit_code; + } + } + } + + if let Some(m) = self.serv_rx.try_iter().next() { + match m { + ServerInitStatus::LoadDatabase => self.init_status("Loading database...", true), + ServerInitStatus::StartSync => self.init_status("Start syncing...", true), + ServerInitStatus::StartAPI => self.init_status("Starting API...", true), + ServerInitStatus::FinishedLoading(s) => { + self.ui.cursive.pop_layer(); + self.ui.show_dialog.store(false, Ordering::Relaxed); + self.server = Some(s) + } + ServerInitStatus::ErrorLoading(e) => { + exit_code = 1; + self.init_error(e); } } } if Utc::now().timestamp() > next_stat_update { next_stat_update = Utc::now().timestamp() + stat_update_interval; - if let Ok(stats) = server.get_server_stats() { - self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap(); + if let Some(server) = &self.server { + if let Ok(stats) = server.get_server_stats() { + self.ui.ui_tx.send(UIMessage::UpdateStatus(stats)).unwrap(); + } } } thread::sleep(delay); } - server.stop(); + exit_code } }