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