Support running as a Windows service.

This commit is contained in:
chao wan 2024-10-25 03:26:05 +08:00
parent 0bf42c53cc
commit f1f3134058
3 changed files with 119 additions and 2 deletions

18
Cargo.lock generated
View File

@ -1623,6 +1623,7 @@ dependencies = [
"url",
"uuid",
"wildmatch",
"windows-service",
"windows-sys 0.52.0",
"winreg 0.52.0",
"zerocopy",
@ -7223,6 +7224,12 @@ dependencies = [
"rustix",
]
[[package]]
name = "widestring"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "wildmatch"
version = "2.3.4"
@ -7344,6 +7351,17 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-service"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24d6bcc7f734a4091ecf8d7a64c5f7d7066f45585c1861eba06449909609c8a"
dependencies = [
"bitflags 2.6.0",
"widestring",
"windows-sys 0.52.0",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@ -191,6 +191,7 @@ windows-sys = { version = "0.52", features = [
] }
encoding = "0.2"
winreg = "0.52"
windows-service = "0.7.0"
[build-dependencies]
tonic-build = "0.12"

View File

@ -4,8 +4,7 @@
mod tests;
use std::{
net::{Ipv4Addr, SocketAddr},
path::PathBuf,
ffi::OsString, net::{Ipv4Addr, SocketAddr}, path::PathBuf
};
#[macro_use]
@ -44,6 +43,9 @@ use crate::{
utils::init_logger,
};
#[cfg(target_os = "windows")]
windows_service::define_windows_service!(ffi_service_main, win_service_main);
#[cfg(feature = "mimalloc")]
use mimalloc_rust::*;
@ -658,6 +660,95 @@ pub fn handle_event(mut events: EventBusSubscriber) -> tokio::task::JoinHandle<(
})
}
#[cfg(target_os = "windows")]
fn win_service_event_loop(
stop_notify: std::sync::Arc<tokio::sync::Notify>,
inst: launcher::NetworkInstance,
status_handle: windows_service::service_control_handler::ServiceStatusHandle,
) {
use tokio::runtime::Runtime;
use std::time::Duration;
use windows_service::service::*;
std::thread::spawn(move || {
let rt = Runtime::new().unwrap();
rt.block_on(async move {
tokio::select! {
res = inst.wait() => {
if let Some(e) = res {
status_handle.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
checkpoint: 0,
wait_hint: Duration::default(),
exit_code: ServiceExitCode::ServiceSpecific(1u32),
process_id: None
}).unwrap();
panic!("launcher error: {:?}", e);
}
},
_ = stop_notify.notified() => {
status_handle.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
checkpoint: 0,
wait_hint: Duration::default(),
exit_code: ServiceExitCode::Win32(0),
process_id: None
}).unwrap();
}
}
});
});
}
#[cfg(target_os = "windows")]
fn win_service_main(_: Vec<OsString>) {
use std::time::Duration;
use windows_service::service_control_handler::*;
use windows_service::service::*;
use std::sync::Arc;
use tokio::sync::Notify;
let cli = Cli::parse();
let cfg = TomlConfigLoader::from(cli);
init_logger(&cfg, false).unwrap();
let stop_notify_send = Arc::new(Notify::new());
let stop_notify_recv = Arc::clone(&stop_notify_send);
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Interrogate => {
ServiceControlHandlerResult::NoError
}
ServiceControl::Stop =>
{
stop_notify_send.notify_one();
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
}
};
let status_handle = register(String::new(), event_handler).expect("register service fail");
let next_status = ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
};
let mut inst = launcher::NetworkInstance::new(cfg).set_fetch_node_info(false);
inst.start().unwrap();
status_handle.set_service_status(next_status).expect("set service status fail");
win_service_event_loop(stop_notify_recv, inst, status_handle);
}
#[tokio::main]
async fn main() {
setup_panic_handler();
@ -665,6 +756,13 @@ async fn main() {
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
rust_i18n::set_locale(&locale);
#[cfg(target_os = "windows")]
{
use windows_service::service_dispatcher;
if let Ok(()) = service_dispatcher::start(String::new(), ffi_service_main) {
return;
}
}
let cli = Cli::parse();
let cfg = TomlConfigLoader::from(cli);
init_logger(&cfg, false).unwrap();