-
Notifications
You must be signed in to change notification settings - Fork 39
feat: add remote-manage #576
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
94d6f96
d57ec08
7a7578c
679fd93
17da896
622819b
2628e43
7160964
c75ad66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -14,17 +14,24 @@ | |||||
|
|
||||||
| use crate::config::{EngineConfig, LogFormat}; | ||||||
| use crate::engine::Engine; | ||||||
| use crate::remote_config::RemoteConfigManager; | ||||||
| use clap::{Arg, Command}; | ||||||
| use std::process; | ||||||
| use tokio::signal::unix::{signal, SignalKind}; | ||||||
| use tokio_util::sync::CancellationToken; | ||||||
| use tracing::{info, Level}; | ||||||
| use tracing_subscriber::fmt; | ||||||
|
|
||||||
| pub struct Cli { | ||||||
| pub config: Option<EngineConfig>, | ||||||
| pub remote_config_manager: Option<RemoteConfigManager>, | ||||||
| } | ||||||
| impl Default for Cli { | ||||||
| fn default() -> Self { | ||||||
| Self { config: None } | ||||||
| Self { | ||||||
| config: None, | ||||||
| remote_config_manager: None, | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -33,54 +40,120 @@ impl Cli { | |||||
| let matches = Command::new("arkflow") | ||||||
| .version("0.4.0-rc1") | ||||||
| .author("chenquan") | ||||||
| .about("High-performance Rust stream processing engine, providing powerful data stream processing capabilities, supporting multiple input/output sources and processors.") | ||||||
| .about("High-performance Rust stream processing engine, providing powerful data stream processing capabilities, supporting multiple input/output sources and processors") | ||||||
| .arg( | ||||||
| Arg::new("config") | ||||||
| .short('c') | ||||||
| .long("config") | ||||||
| .value_name("FILE") | ||||||
| .help("Specify the profile path.") | ||||||
| .required(true), | ||||||
| .help("Specify the profile path") | ||||||
| ) | ||||||
| .arg( | ||||||
| Arg::new("validate") | ||||||
| .short('v') | ||||||
| .long("validate") | ||||||
| .help("Only the profile is verified, not the engine is started.") | ||||||
| .help("Only the profile is verified, not the engine is started") | ||||||
| .action(clap::ArgAction::SetTrue), | ||||||
| ) | ||||||
| .subcommand( | ||||||
| Command::new("remote").about("Use remote configuration for automatic stream management") | ||||||
| .arg( | ||||||
| Arg::new("url") | ||||||
| .long("url") | ||||||
| .value_name("URL") | ||||||
| .help("Remote configuration API endpoint URL for automatic stream management") | ||||||
| .required( true) | ||||||
| ) | ||||||
| .arg( | ||||||
| Arg::new("interval") | ||||||
| .long("interval") | ||||||
| .value_name("SECONDS") | ||||||
| .help("Interval in seconds for polling remote configuration") | ||||||
| .default_value("30"), | ||||||
| ) | ||||||
| .arg( | ||||||
| Arg::new("token") | ||||||
| .long("token") | ||||||
| .value_name("TOKEN") | ||||||
| .help("Authentication token for remote configuration API") | ||||||
| .required( true), | ||||||
| )) | ||||||
| .get_matches(); | ||||||
|
|
||||||
| // Get the profile path | ||||||
| let config_path = matches.get_one::<String>("config").unwrap(); | ||||||
| // Check if using remote configuration | ||||||
| if let Some(remote) = matches.subcommand_matches("remote") { | ||||||
| // Initialize remote configuration manager | ||||||
| let interval = remote | ||||||
| .get_one::<String>("interval") | ||||||
| .and_then(|s| s.parse::<u64>().ok()) | ||||||
| .unwrap_or(30); | ||||||
| let token = remote.get_one::<String>("token").cloned(); | ||||||
| let remote_url = remote | ||||||
| .get_one::<String>("url") | ||||||
| .expect("Remote configuration URL not found"); | ||||||
|
|
||||||
| // Get the profile path | ||||||
| let config = match EngineConfig::from_file(config_path) { | ||||||
| Ok(config) => config, | ||||||
| Err(e) => { | ||||||
| println!("Failed to load configuration file: {}", e); | ||||||
| process::exit(1); | ||||||
| let remote_manager = RemoteConfigManager::new(remote_url.clone(), interval, token); | ||||||
|
|
||||||
| self.remote_config_manager.replace(remote_manager); | ||||||
| info!("Using remote configuration from: {}", remote_url); | ||||||
| } else { | ||||||
| // Use local configuration file | ||||||
| let config_path = matches | ||||||
| .get_one::<String>("config") | ||||||
| .ok_or("Configuration not found")?; | ||||||
|
|
||||||
| let config = match EngineConfig::from_file(config_path) { | ||||||
| Ok(config) => config, | ||||||
| Err(e) => { | ||||||
| println!("Failed to load configuration file: {}", e); | ||||||
| process::exit(1); | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| // If you just verify the configuration, exit it | ||||||
| if matches.get_flag("validate") { | ||||||
| info!("The config is validated."); | ||||||
| return Ok(()); | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| // If you just verify the configuration, exit it | ||||||
| if matches.get_flag("validate") { | ||||||
| info!("The config is validated."); | ||||||
| return Ok(()); | ||||||
| self.config = Some(config); | ||||||
| } | ||||||
| self.config = Some(config); | ||||||
| Ok(()) | ||||||
| } | ||||||
| pub async fn run(&self) -> Result<(), Box<dyn std::error::Error>> { | ||||||
| // Initialize the logging system | ||||||
| let config = self.config.clone().unwrap(); | ||||||
| init_logging(&config); | ||||||
| let engine = Engine::new(config); | ||||||
| engine.run().await?; | ||||||
| let token = CancellationToken::new(); | ||||||
|
|
||||||
| if let Some(remote_manager) = &self.remote_config_manager { | ||||||
| // Run with remote configuration management | ||||||
| remote_manager.run(token.clone()).await?; | ||||||
| } else { | ||||||
| // Run with local configuration | ||||||
| let config = self.config.clone().unwrap(); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid unwrap() on potentially None config. While the logic ensures config is Some when remote_config_manager is None, using Apply this diff to handle the None case explicitly: - let config = self.config.clone().unwrap();
+ let config = self.config.clone().ok_or("Configuration not found")?;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| init_logging(&config); | ||||||
| let engine = Engine::new(config); | ||||||
| engine.run(token.clone()).await?; | ||||||
| } | ||||||
|
|
||||||
| // Set up signal handlers | ||||||
| let mut sigint = signal(SignalKind::interrupt()).expect("Failed to set signal handler"); | ||||||
| let mut sigterm = signal(SignalKind::terminate()).expect("Failed to set signal handler"); | ||||||
|
|
||||||
| tokio::spawn(async move { | ||||||
| tokio::select! { | ||||||
| _ = sigint.recv() => { | ||||||
| info!("Received SIGINT, exiting..."); | ||||||
|
|
||||||
| }, | ||||||
| _ = sigterm.recv() => { | ||||||
| info!("Received SIGTERM, exiting..."); | ||||||
| } | ||||||
| } | ||||||
| token.cancel(); | ||||||
| }); | ||||||
|
Comment on lines
+137
to
+152
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Signal handlers are set up after the run completes (unreachable), breaking graceful shutdown You Move the signal setup before awaiting pub async fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
- let token = CancellationToken::new();
+ let token = CancellationToken::new();
+
+ // Set up signal handlers BEFORE running the engine/remote manager
+ let token_for_signals = token.clone();
+ #[cfg(unix)]
+ tokio::spawn(async move {
+ let mut sigint = signal(SignalKind::interrupt()).expect("Failed to set signal handler");
+ let mut sigterm = signal(SignalKind::terminate()).expect("Failed to set signal handler");
+ tokio::select! {
+ _ = sigint.recv() => {
+ info!("Received SIGINT, exiting...");
+ },
+ _ = sigterm.recv() => {
+ info!("Received SIGTERM, exiting...");
+ }
+ }
+ token_for_signals.cancel();
+ });
+
+ #[cfg(not(unix))]
+ tokio::spawn(async move {
+ if tokio::signal::ctrl_c().await.is_ok() {
+ info!("Received Ctrl+C, exiting...");
+ }
+ token_for_signals.cancel();
+ });
- if let Some(remote_manager) = &self.remote_config_manager {
+ if let Some(remote_manager) = &self.remote_config_manager {
// Run with remote configuration management
remote_manager.run(token.clone()).await?;
} else {
// Run with local configuration
let config = self.config.clone().unwrap();
init_logging(&config);
let engine = Engine::new(config);
engine.run(token.clone()).await?;
}
- // Set up signal handlers
- let mut sigint = signal(SignalKind::interrupt()).expect("Failed to set signal handler");
- let mut sigterm = signal(SignalKind::terminate()).expect("Failed to set signal handler");
-
- tokio::spawn(async move {
- tokio::select! {
- _ = sigint.recv() => {
- info!("Received SIGINT, exiting...");
-
- },
- _ = sigterm.recv() => {
- info!("Received SIGTERM, exiting...");
- }
- }
- token.cancel();
- });
Ok(())
}🤖 Prompt for AI Agents |
||||||
| Ok(()) | ||||||
| } | ||||||
| } | ||||||
| fn init_logging(config: &EngineConfig) -> () { | ||||||
| pub(crate) fn init_logging(config: &EngineConfig) -> () { | ||||||
| let log_level = match config.logging.level.as_str() { | ||||||
| "trace" => Level::TRACE, | ||||||
| "debug" => Level::DEBUG, | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compilation bug: using
ok_or("...")?with&strwon't compile&strdoesn’t implementstd::error::Error, so?cannot convert it intoBox<dyn std::error::Error>. Use an error type that implementsError(e.g.,std::io::Error) or handle it explicitly.Apply this diff:
📝 Committable suggestion
🤖 Prompt for AI Agents