diff --git a/CHANGELOG.md b/CHANGELOG.md index c1cb5c8828..0a48c6dbf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,14 @@ How to resolve the error: Run the command with '--ic' flag if you want to manage the cycles on the mainnet. ``` +### chore: improve `dfx start` messages. + +For `dfx start`, show below messages to users to indicate what to do next. +``` +Success! The dfx server is running. +You must open a new terminal to continue developing. If you'd prefer to stop, quit with 'Ctrl-C'. +``` + # 0.24.2 ### feat: Support canister log allowed viewer list diff --git a/e2e/tests-dfx/start.bash b/e2e/tests-dfx/start.bash index 3b6fe8207c..bdfd7d25d6 100644 --- a/e2e/tests-dfx/start.bash +++ b/e2e/tests-dfx/start.bash @@ -60,7 +60,8 @@ teardown() { } @test "start and stop outside project" { - dfx_start + assert_command dfx_start + assert_contains "Success! The dfx server is running in the background." mkdir subdir cd subdir || exit 1 diff --git a/src/dfx/src/actors/mod.rs b/src/dfx/src/actors/mod.rs index 721f35a73f..4cc8b1c839 100644 --- a/src/dfx/src/actors/mod.rs +++ b/src/dfx/src/actors/mod.rs @@ -13,6 +13,7 @@ use dfx_core::config::model::replica_config::ReplicaConfig; use fn_error_context::context; use pocketic_proxy::signals::PortReadySubscribe; use pocketic_proxy::{PocketIcProxy, PocketIcProxyConfig}; +use post_start::PostStart; use std::fs; use std::path::PathBuf; @@ -22,6 +23,7 @@ pub mod btc_adapter; pub mod canister_http_adapter; pub mod pocketic; pub mod pocketic_proxy; +pub mod post_start; pub mod replica; mod shutdown; pub mod shutdown_controller; @@ -214,3 +216,17 @@ pub fn start_pocketic_actor( }; Ok(pocketic::PocketIc::new(actor_config).start()) } + +#[context("Failed to start PostStart actor.")] +pub fn start_post_start_actor( + env: &dyn Environment, + background: bool, + pocketic_proxy: Option>, +) -> DfxResult> { + let config = post_start::Config { + logger: env.get_logger().clone(), + background, + pocketic_proxy, + }; + Ok(PostStart::new(config).start()) +} diff --git a/src/dfx/src/actors/pocketic_proxy.rs b/src/dfx/src/actors/pocketic_proxy.rs index ba86f0d580..4f6da239bf 100644 --- a/src/dfx/src/actors/pocketic_proxy.rs +++ b/src/dfx/src/actors/pocketic_proxy.rs @@ -1,4 +1,5 @@ use crate::actors::pocketic_proxy::signals::{PortReadySignal, PortReadySubscribe}; +use crate::actors::post_start::signals::{PocketIcProxyReadySignal, PocketIcProxyReadySubscribe}; use crate::actors::shutdown::{wait_for_child_or_receiver, ChildOrReceiver}; use crate::actors::shutdown_controller::signals::outbound::Shutdown; use crate::actors::shutdown_controller::signals::ShutdownSubscribe; @@ -70,6 +71,9 @@ pub struct PocketIcProxy { stop_sender: Option>, thread_join: Option>, + + /// Ready Signal subscribers. + ready_subscribers: Vec>, } impl PocketIcProxy { @@ -81,10 +85,11 @@ impl PocketIcProxy { stop_sender: None, thread_join: None, logger, + ready_subscribers: Vec::new(), } } - fn start_pocketic_proxy(&mut self, replica_url: Url) -> DfxResult { + fn start_pocketic_proxy(&mut self, replica_url: Url, addr: Addr) -> DfxResult { let logger = self.logger.clone(); let config = &self.config.pocketic_proxy_config; let pocketic_proxy_path = self.config.pocketic_proxy_path.clone(); @@ -100,6 +105,7 @@ impl PocketIcProxy { pocketic_proxy_path, pocketic_proxy_pid_path, pocketic_proxy_port_path, + addr, receiver, config.verbose, config.domains.clone(), @@ -164,7 +170,7 @@ impl Actor for PocketIcProxy { .do_send(ShutdownSubscribe(ctx.address().recipient::())); if let Some(replica_url) = &self.config.pocketic_proxy_config.replica_url { - self.start_pocketic_proxy(replica_url.clone()) + self.start_pocketic_proxy(replica_url.clone(), ctx.address()) .expect("Could not start PocketIC HTTP gateway"); } } @@ -179,7 +185,7 @@ impl Actor for PocketIcProxy { impl Handler for PocketIcProxy { type Result = (); - fn handle(&mut self, msg: PortReadySignal, _ctx: &mut Self::Context) { + fn handle(&mut self, msg: PortReadySignal, ctx: &mut Self::Context) { debug!( self.logger, "replica ready on {}, so re/starting HTTP gateway", msg.url @@ -189,11 +195,29 @@ impl Handler for PocketIcProxy { let replica_url = Url::parse(&msg.url).unwrap(); - self.start_pocketic_proxy(replica_url) + self.start_pocketic_proxy(replica_url, ctx.address()) .expect("Could not start PocketIC HTTP gateway"); } } +impl Handler for PocketIcProxy { + type Result = (); + + fn handle(&mut self, msg: PocketIcProxyReadySubscribe, _ctx: &mut Self::Context) { + self.ready_subscribers.push(msg.0); + } +} + +impl Handler for PocketIcProxy { + type Result = (); + + fn handle(&mut self, _msg: PocketIcProxyReadySignal, _ctx: &mut Self::Context) { + for sub in &self.ready_subscribers { + sub.do_send(PocketIcProxyReadySignal); + } + } +} + impl Handler for PocketIcProxy { type Result = ResponseActFuture>; @@ -217,6 +241,7 @@ fn pocketic_proxy_start_thread( pocketic_proxy_path: PathBuf, pocketic_proxy_pid_path: PathBuf, pocketic_proxy_port_path: PathBuf, + addr: Addr, receiver: Receiver<()>, verbose: bool, domains: Option>, @@ -277,6 +302,9 @@ fn pocketic_proxy_start_thread( } info!(logger, "Replica API running on {address}"); + // Send PocketIcProxyReadySignal to PocketIcProxy. + addr.do_send(PocketIcProxyReadySignal); + // This waits for the child to stop, or the receiver to receive a message. // We don't restart pocket-ic if done = true. match wait_for_child_or_receiver(&mut child, &receiver) { diff --git a/src/dfx/src/actors/post_start.rs b/src/dfx/src/actors/post_start.rs new file mode 100644 index 0000000000..35222e6e57 --- /dev/null +++ b/src/dfx/src/actors/post_start.rs @@ -0,0 +1,62 @@ +use crate::actors::pocketic_proxy::PocketIcProxy; +use crate::actors::post_start::signals::{PocketIcProxyReadySignal, PocketIcProxyReadySubscribe}; +use actix::{Actor, Addr, AsyncContext, Context, Handler}; +use slog::{info, Logger}; + +pub mod signals { + use actix::prelude::*; + + #[derive(Message)] + #[rtype(result = "()")] + pub struct PocketIcProxyReadySignal; + + #[derive(Message)] + #[rtype(result = "()")] + pub struct PocketIcProxyReadySubscribe(pub Recipient); +} + +pub struct Config { + pub logger: Logger, + pub background: bool, + pub pocketic_proxy: Option>, +} + +pub struct PostStart { + config: Config, +} + +impl PostStart { + pub fn new(config: Config) -> Self { + Self { config } + } +} + +impl Actor for PostStart { + type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + // Register the PostStart recipent to PocketIcProxy. + if let Some(pocketic_proxy) = &self.config.pocketic_proxy { + pocketic_proxy.do_send(PocketIcProxyReadySubscribe(ctx.address().recipient())); + } + } +} + +impl Handler for PostStart { + type Result = (); + + fn handle(&mut self, _msg: PocketIcProxyReadySignal, _ctx: &mut Self::Context) -> Self::Result { + let logger = &self.config.logger; + if self.config.background { + info!( + logger, + "Success! The dfx server is running in the background." + ) + } else { + info!( + logger, + "Success! The dfx server is running.\nYou must open a new terminal to continue developing. If you'd prefer to stop, quit with 'Ctrl-C'." + ) + } + } +} diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index 4e556cbecd..909058dfa4 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -1,7 +1,8 @@ use crate::actors::pocketic_proxy::{signals::PortReadySubscribe, PocketIcProxyConfig}; use crate::actors::{ start_btc_adapter_actor, start_canister_http_adapter_actor, start_pocketic_actor, - start_pocketic_proxy_actor, start_replica_actor, start_shutdown_controller, + start_pocketic_proxy_actor, start_post_start_actor, start_replica_actor, + start_shutdown_controller, }; use crate::config::dfx_version_str; use crate::error_invalid_argument; @@ -49,6 +50,10 @@ pub struct StartOpts { #[arg(long)] background: bool, + /// Indicates if the actual dfx process is running in the background. + #[arg(long, env = "DFX_RUNNING_IN_BACKGROUND", hide = true)] + running_in_background: bool, + /// Cleans the state of the current project. #[arg(long)] clean: bool, @@ -138,6 +143,7 @@ pub fn exec( StartOpts { host, background, + running_in_background, clean, force, bitcoin_node, @@ -413,7 +419,10 @@ pub fn exec( pocketic_proxy_pid_file_path, pocketic_proxy_port_file_path, )?; - Ok::<_, Error>(proxy) + + let post_start = start_post_start_actor(env, running_in_background, Some(proxy))?; + + Ok::<_, Error>(post_start) })?; system.run()?; @@ -574,7 +583,8 @@ fn send_background() -> DfxResult<()> { .skip(1) .filter(|a| !a.eq("--background")) .filter(|a| !a.eq("--clean")), - ); + ) + .env("DFX_RUNNING_IN_BACKGROUND", "true"); // Set the `DFX_RUNNING_IN_BACKGROUND` environment variable which will be used by the second start. cmd.spawn().context("Failed to spawn child process.")?; Ok(())