Forráskód Böngészése

util/cli/async_daemonize: use log_file to explicitly write to, impl signal handler using async signal hooks

aggstam 3 éve
szülő
commit
a63a808992
5 módosított fájl, 127 hozzáadás és 49 törlés
  1. 2 1
      Cargo.lock
  2. 7 4
      bin/darkfid2/Cargo.toml
  3. 1 1
      bin/darkfid2/darkfid_config.toml
  4. 14 16
      bin/darkfid2/src/main.rs
  5. 103 27
      src/util/cli.rs

+ 2 - 1
Cargo.lock

@@ -1802,13 +1802,14 @@ name = "darkfid2"
 version = "0.4.1"
 dependencies = [
  "async-std",
- "ctrlc",
  "darkfi",
  "darkfi-contract-test-harness",
  "darkfi-sdk",
  "easy-parallel",
  "log",
  "serde",
+ "signal-hook",
+ "signal-hook-async-std",
  "simplelog",
  "sled",
  "smol",

+ 7 - 4
bin/darkfid2/Cargo.toml

@@ -9,15 +9,18 @@ license = "AGPL-3.0-only"
 edition = "2021"
 
 [dependencies]
-async-std = "1.12.0"
-ctrlc = { version = "3.4.0", features = ["termination"] }
 darkfi = {path = "../../", features = ["async-runtime", "util"]}
 darkfi-contract-test-harness = {path = "../../src/contract/test-harness"}
 darkfi-sdk = {path = "../../src/sdk"}
-easy-parallel = "3.3.0"
 log = "0.4.19"
-simplelog = "0.12.1"
 sled = "0.34.7"
+
+# Daemon
+async-std = "1.12.0"
+easy-parallel = "3.3.0"
+signal-hook-async-std = "0.2.2"
+signal-hook = "0.3.15"
+simplelog = "0.12.1"
 smol = "1.3.0"
 
 # Argument parsing

+ 1 - 1
bin/darkfid2/darkfid_config.toml

@@ -7,4 +7,4 @@
 ## uncommenting, or by using the command-line.
 
 # Enable testing mode for local testing
-testing_node = false
+testing_mode = false

+ 14 - 16
bin/darkfid2/src/main.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use async_std::sync::Arc;
+use async_std::{stream::StreamExt, sync::Arc};
 use log::info;
 use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
 
@@ -28,6 +28,7 @@ use darkfi::{
     validator::{Validator, ValidatorConfig, ValidatorPtr},
     Result,
 };
+use darkfi_contract_test_harness::vks;
 
 #[cfg(test)]
 mod tests;
@@ -45,7 +46,11 @@ struct Args {
 
     #[structopt(long)]
     /// Enable testing mode for local testing
-    testing_node: bool,
+    testing_mode: bool,
+
+    #[structopt(short, long)]
+    /// Set log file to ouput into
+    log: Option<String>,
 
     #[structopt(short, parse(from_occurrences))]
     /// Increase verbosity (-vvv supported)
@@ -66,25 +71,20 @@ async_daemonize!(realmain);
 async fn realmain(args: Args, _ex: Arc<smol::Executor<'_>>) -> Result<()> {
     info!("Initializing DarkFi node...");
 
-    // We use this handler to block this function after detaching all
-    // tasks, and to catch a shutdown signal, where we can clean up and
-    // exit gracefully.
-    let (signal, shutdown) = smol::channel::bounded::<()>(1);
-    ctrlc::set_handler(move || {
-        async_std::task::block_on(signal.send(())).unwrap();
-    })
-    .unwrap();
+    // Signal handling for graceful termination.
+    let (signals_handler, signals_task) = SignalHandler::new()?;
 
     // NOTE: everything is dummy for now
     // Initialize or open sled database
     let sled_db = sled::Config::new().temporary(true).open()?;
+    vks::inject(&sled_db)?;
 
     // Initialize validator configuration
     let genesis_block = BlockInfo::default();
     let time_keeper = TimeKeeper::new(genesis_block.header.timestamp, 10, 90, 0);
-    let config = ValidatorConfig::new(time_keeper, genesis_block, vec![], args.testing_node);
+    let config = ValidatorConfig::new(time_keeper, genesis_block, vec![], args.testing_mode);
 
-    if args.testing_node {
+    if args.testing_mode {
         info!("Node is configured to run in testing mode!");
     }
 
@@ -95,10 +95,8 @@ async fn realmain(args: Args, _ex: Arc<smol::Executor<'_>>) -> Result<()> {
     let _darkfid = Darkfid::new(validator).await;
     info!("Node initialized successfully!");
 
-    // Wait for SIGINT
-    shutdown.recv().await?;
-    print!("\r");
+    // Wait for termination signal
+    signals_handler.wait_termination(signals_task).await?;
     info!("Caught termination signal, cleaning up and exiting...");
-
     Ok(())
 }

+ 103 - 27
src/util/cli.rs

@@ -119,6 +119,8 @@ pub fn get_log_config(verbosity_level: u8) -> simplelog::Config {
 /// async-std = "1.12.0"
 /// darkfi = { path = "../../", features = ["util"] }
 /// easy-parallel = "3.2.0"
+/// signal-hook-async-std = "0.2.2"
+/// signal-hook = "0.3.15"
 /// simplelog = "0.12.0"
 /// smol = "1.2.5"
 ///
@@ -130,7 +132,7 @@ pub fn get_log_config(verbosity_level: u8) -> simplelog::Config {
 ///
 /// Example usage:
 /// ```
-/// use async_std::sync::Arc;
+/// use async_std::{stream::StreamExt, sync::Arc};
 //  use darkfi::{async_daemonize, cli_desc, Result};
 /// use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
 ///
@@ -145,6 +147,10 @@ pub fn get_log_config(verbosity_level: u8) -> simplelog::Config {
 ///     /// Configuration file to use
 ///     config: Option<String>,
 ///
+///     #[structopt(short, long)]
+///     /// Set log file to ouput into
+///     log: Option<String>,
+///
 ///     #[structopt(short, parse(from_occurrences))]
 ///     /// Increase verbosity (-vvv supported)
 ///     verbose: u8,
@@ -169,33 +175,27 @@ macro_rules! async_daemonize {
             let log_level = darkfi::util::cli::get_log_level(args.verbose);
             let log_config = darkfi::util::cli::get_log_config(args.verbose);
 
-            /* FIXME: This is an issue. We should only log when explicitly told to
-            let log_file_path = match std::env::var("DARKFI_LOG") {
-                Ok(p) => p,
-                Err(_) => {
-                    let bin_name = if let Some(bin_name) = option_env!("CARGO_BIN_NAME") {
-                        bin_name
-                    } else {
-                        "darkfi"
-                    };
-                    std::fs::create_dir_all(darkfi::util::path::expand_path("~/.local/darkfi")?)?;
-                    format!("~/.local/darkfi/{}.log", bin_name)
-                }
-            };
-
-            let log_file_path = darkfi::util::path::expand_path(&log_file_path)?;
-            let log_file = std::fs::File::create(log_file_path)?;
-            */
+            // Setup terminal logger
+            let term_logger = simplelog::TermLogger::new(
+                log_level,
+                log_config.clone(),
+                simplelog::TerminalMode::Mixed,
+                simplelog::ColorChoice::Auto,
+            );
 
-            simplelog::CombinedLogger::init(vec![
-                simplelog::TermLogger::new(
-                    log_level,
-                    log_config.clone(),
-                    simplelog::TerminalMode::Mixed,
-                    simplelog::ColorChoice::Auto,
-                ),
-                //simplelog::WriteLogger::new(log_level, log_config, log_file),
-            ])?;
+            // If a log file has been configured, also create a write logger.
+            // Otherwise, output to terminal logger only.
+            match args.log {
+                Some(ref log_path) => {
+                    let log_path = darkfi::util::path::expand_path(log_path)?;
+                    let log_file = std::fs::File::create(log_path)?;
+                    let write_logger = simplelog::WriteLogger::new(log_level, log_config, log_file);
+                    simplelog::CombinedLogger::init(vec![term_logger, write_logger])?;
+                }
+                None => {
+                    simplelog::CombinedLogger::init(vec![term_logger])?;
+                }
+            }
 
             // https://docs.rs/smol/latest/smol/struct.Executor.html#examples
             let n_threads = std::thread::available_parallelism().unwrap().get();
@@ -215,6 +215,82 @@ macro_rules! async_daemonize {
 
             result
         }
+
+        /// Auxiliary structure used to keep track of signals
+        struct SignalHandler {
+            /// Termination signal channel receiver
+            term_rx: smol::channel::Receiver<()>,
+            /// Signals handle
+            handle: signal_hook_async_std::Handle,
+            /// SIGHUP subscriber to retrieve new configuration,
+            sighup_sub: darkfi::system::SubscriberPtr<Args>,
+        }
+
+        impl SignalHandler {
+            fn new() -> Result<(Self, async_std::task::JoinHandle<Result<()>>)> {
+                let (term_tx, term_rx) = smol::channel::bounded::<()>(1);
+                let signals = signal_hook_async_std::Signals::new([
+                    signal_hook::consts::SIGHUP,
+                    signal_hook::consts::SIGTERM,
+                    signal_hook::consts::SIGINT,
+                    signal_hook::consts::SIGQUIT,
+                ])?;
+                let handle = signals.handle();
+                let sighup_sub = darkfi::system::Subscriber::new();
+                let signals_task =
+                    async_std::task::spawn(handle_signals(signals, term_tx, sighup_sub.clone()));
+
+                Ok((Self { term_rx, handle, sighup_sub }, signals_task))
+            }
+
+            /// Handler waits for termination signal
+            async fn wait_termination(
+                &self,
+                signals_task: async_std::task::JoinHandle<Result<()>>,
+            ) -> Result<()> {
+                self.term_rx.recv().await?;
+                print!("\r");
+                self.handle.close();
+                signals_task.await?;
+
+                Ok(())
+            }
+        }
+
+        /// Auxiliary task to handle SIGHUP, SIGTERM, SIGINT and SIGQUIT signals
+        async fn handle_signals(
+            mut signals: signal_hook_async_std::Signals,
+            term_tx: smol::channel::Sender<()>,
+            subscriber: darkfi::system::SubscriberPtr<Args>,
+        ) -> Result<()> {
+            while let Some(signal) = signals.next().await {
+                match signal {
+                    signal_hook::consts::SIGHUP => {
+                        let args = Args::from_args_with_toml("").unwrap();
+                        let cfg_path =
+                            darkfi::util::path::get_config_path(args.config, CONFIG_FILE)?;
+                        darkfi::util::cli::spawn_config(
+                            &cfg_path,
+                            CONFIG_FILE_CONTENTS.as_bytes(),
+                        )?;
+                        let args = Args::from_args_with_toml(&std::fs::read_to_string(cfg_path)?);
+                        if args.is_err() {
+                            println!("handle_signals():: Error parsing the config file");
+                            continue
+                        }
+                        subscriber.notify(args.unwrap()).await;
+                    }
+                    signal_hook::consts::SIGTERM |
+                    signal_hook::consts::SIGINT |
+                    signal_hook::consts::SIGQUIT => {
+                        term_tx.send(()).await?;
+                    }
+
+                    _ => println!("handle_signals():: Unsupported signal"),
+                }
+            }
+            Ok(())
+        }
     };
 }