pispas_service\service/
pispas_service.rs1use parking_lot::RwLock;
2use std::{
3 sync::Arc,
4 sync::atomic::{AtomicBool, Ordering},
5};
6#[cfg(windows)]
7use std::thread::sleep;
8#[cfg(windows)]
9use std::time::Duration;
10use easy_trace::instruments::tracing::{info};
11
12#[cfg(windows)]
14use {
15 std::ffi::OsString,
16 windows_service::{
17 define_windows_service,
18 service::{
19 ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
20 ServiceType,
21 },
22 service_control_handler::{self, ServiceControlHandlerResult},
23 service_dispatcher, Result as WindowsResult,
24 },
25};
26
27#[cfg(unix)]
29use {
30 signal_hook::{consts::SIGTERM, consts::SIGINT, consts::SIGHUP, iterator::Signals},
31 std::thread,
32};
33#[cfg(windows)]
34use easy_trace::instruments::tracing;
35#[cfg(windows)]
36use easy_trace::instruments::tracing::error;
37
38#[cfg(windows)]
39const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
40
41#[derive(Debug)]
43pub enum ServiceError {
44 #[cfg(windows)]
45 Windows(windows_service::Error),
46 #[cfg(unix)]
47 Unix(std::io::Error),
48 _General(String),
49}
50
51impl std::fmt::Display for ServiceError {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 #[cfg(windows)]
55 ServiceError::Windows(e) => write!(f, "Windows service error: {}", e),
56 #[cfg(unix)]
57 ServiceError::Unix(e) => write!(f, "Unix daemon error: {}", e),
58 ServiceError::_General(e) => write!(f, "Service error: {}", e),
59 }
60 }
61}
62
63impl std::error::Error for ServiceError {}
64
65#[cfg(windows)]
66impl From<windows_service::Error> for ServiceError {
67 fn from(error: windows_service::Error) -> Self {
68 ServiceError::Windows(error)
69 }
70}
71
72#[cfg(unix)]
73impl From<std::io::Error> for ServiceError {
74 fn from(error: std::io::Error) -> Self {
75 ServiceError::Unix(error)
76 }
77}
78
79#[cfg(windows)]
81pub fn run() -> Result<(), ServiceError> {
82 service_dispatcher::start(sharing::SERVICE_NAME, ffi_service_main)
83 .map_err(ServiceError::from)
84}
85
86#[cfg(unix)]
88pub fn run() -> Result<(), ServiceError> {
89 info!("Starting Unix daemon");
90 run_daemon()
91}
92
93#[cfg(windows)]
94define_windows_service!(ffi_service_main, my_service_main);
95
96#[cfg(windows)]
97pub fn my_service_main(_arguments: Vec<OsString>) {
98 info!("Call my_service_main with {:?}", _arguments);
99 if let Err(e) = run_service() {
100 tracing::error!("Error starting service: {}", e);
101 }
102}
103
104#[cfg(unix)]
106pub fn run_daemon() -> Result<(), ServiceError> {
107 let cancel_token = Arc::new(AtomicBool::new(false));
108 let signal_cancel = cancel_token.clone();
109
110 let mut signals = Signals::new(&[SIGTERM, SIGINT, SIGHUP])
112 .map_err(ServiceError::from)?;
113
114 thread::spawn(move || {
115 for sig in signals.forever() {
116 info!("Received signal: {}", sig);
117 match sig {
118 SIGTERM | SIGINT => {
119 info!("Received termination signal, shutting down gracefully");
120 signal_cancel.store(true, Ordering::Relaxed);
121 break;
122 }
123 SIGHUP => {
124 info!("Received SIGHUP, could implement config reload here");
125 }
127 _ => {}
128 }
129 }
130 });
131 println!("Running service in Unix mode");
132 run_service_common(cancel_token)
133}
134
135#[cfg(windows)]
136pub fn run_service() -> WindowsResult<()> {
137 let cancel_token = Arc::new(AtomicBool::new(false));
138 let thread_cancel = cancel_token.clone();
139
140 let event_handler = move |control_event| -> ServiceControlHandlerResult {
142 match control_event {
143 ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
144 ServiceControl::Stop => {
145 info!("Received stop signal");
146 thread_cancel.store(true, Ordering::Relaxed);
147 sleep(Duration::from_secs(2));
148 ServiceControlHandlerResult::NoError
149 }
150 _ => ServiceControlHandlerResult::NotImplemented,
151 }
152 };
153
154 let status_handle = service_control_handler::register(sharing::SERVICE_NAME, event_handler)?;
155
156 status_handle.set_service_status(ServiceStatus {
158 service_type: SERVICE_TYPE,
159 current_state: ServiceState::Running,
160 controls_accepted: ServiceControlAccept::STOP,
161 exit_code: ServiceExitCode::Win32(0),
162 checkpoint: 0,
163 wait_hint: Duration::default(),
164 process_id: None,
165 })?;
166
167 if let Err(e) = run_service_common(cancel_token) {
169 error!("Service error: {}", e);
170 }
171
172 status_handle.set_service_status(ServiceStatus {
174 service_type: SERVICE_TYPE,
175 current_state: ServiceState::Stopped,
176 controls_accepted: ServiceControlAccept::empty(),
177 exit_code: ServiceExitCode::Win32(0),
178 checkpoint: 0,
179 wait_hint: Duration::default(),
180 process_id: None,
181 })?;
182
183 Ok(())
184}
185
186fn run_service_common(cancel_token: Arc<AtomicBool>) -> Result<(), ServiceError> {
188 info!("Starting service");
189
190 let server = Arc::new(RwLock::new(crate::service::server::Server::new()));
191
192 let service_cancel_token = cancel_token.clone();
194
195 info!("Starting service controller");
196
197 crate::service::server::Server::run_server(server.clone(), service_cancel_token);
199
200 info!("Service has been stopped");
201 Ok(())
202}
203
204#[cfg(not(any(windows, unix)))]
206pub fn run() -> Result<(), ServiceError> {
207 Err(ServiceError::General("Platform not supported".to_string()))
208}
209
210pub fn run_direct() -> Result<(), ServiceError> {
212 info!("Running in direct mode");
213 let cancel_token = Arc::new(AtomicBool::new(false));
214
215 #[cfg(unix)]
216 {
217 let signal_cancel = cancel_token.clone();
219 let mut signals = Signals::new(&[SIGTERM, SIGINT])
220 .map_err(ServiceError::from)?;
221
222 thread::spawn(move || {
223 for sig in signals.forever() {
224 info!("Received signal: {}, shutting down", sig);
225 signal_cancel.store(true, Ordering::Relaxed);
226 break;
227 }
228 });
229 }
230
231 run_service_common(cancel_token)
232}