1use std::{
2 sync::{Arc, Weak, atomic::Ordering},
3 time::Duration,
4};
5
6use futures::{FutureExt, future::join, pin_mut};
7#[cfg(all(feature = "systemd", target_os = "linux"))]
8use sd_notify::{NotifyState, notify, notify_and_unset_env, watchdog_enabled};
9use tuwunel_core::{
10 Error, Result, Server, debug, debug_error, debug_info, error, info, utils::BoolExt,
11};
12use tuwunel_service::Services;
13
14use crate::{handle::ServerHandle, serve};
15
16#[tracing::instrument(skip_all)]
18pub(crate) async fn run(services: Arc<Services>) -> Result {
19 let server = &services.server;
20 debug!("Start");
21
22 tuwunel_admin::init(&services.admin);
24
25 services.admin.startup_execute().await?;
27
28 let handle = ServerHandle::new();
30 let sigs = server
31 .runtime()
32 .spawn(signal(server.clone(), handle.clone()));
33 #[cfg(all(feature = "systemd", target_os = "linux"))]
34 let watchdog = server.runtime().spawn(start_systemd_watchdog());
35
36 let non_listener = services
37 .config
38 .listening
39 .is_false()
40 .then_async(|| server.until_shutdown().map(Ok));
41
42 let listener = services.config.listening.then_async(|| {
43 server
44 .runtime()
45 .spawn(serve::serve(services.clone(), handle))
46 .map(|res| res.map_err(Error::from).unwrap_or_else(Err))
47 });
48
49 debug!("Running");
51 pin_mut!(listener, non_listener);
52 let res = tokio::select! {
53 res = join(&mut listener, &mut non_listener) => {
54 res.0.unwrap_or(res.1.unwrap_or(Ok(())))
55 },
56 res = services.poll() => {
57 server.until_shutdown().await;
58 handle_services_finish(server, res, listener.await)
59 },
60 };
61
62 #[cfg(all(feature = "systemd", target_os = "linux"))]
64 {
65 watchdog.abort();
66 _ = watchdog.await;
67 };
68
69 sigs.abort();
70 _ = sigs.await;
71
72 tuwunel_admin::fini(&services.admin);
74
75 debug_info!("Finish");
76 res
77}
78
79#[tracing::instrument(skip_all)]
81pub(crate) async fn start(server: Arc<Server>) -> Result<Arc<Services>> {
82 debug!("Starting...");
83
84 #[cfg(all(feature = "systemd", target_os = "linux"))]
85 let keepalive = server.runtime().spawn(extend_systemd_startup());
86
87 let services = async move { Services::build(server).await?.start().await }.await;
88
89 #[cfg(all(feature = "systemd", target_os = "linux"))]
90 {
91 keepalive.abort();
92 _ = keepalive.await;
93 };
94
95 let services = services?;
96
97 #[cfg(all(feature = "systemd", target_os = "linux"))]
100 notify(&[NotifyState::Ready, NotifyState::Status("Running")])
101 .expect("failed to notify systemd of ready state");
102
103 debug!("Started");
104 Ok(services)
105}
106
107#[tracing::instrument(skip_all)]
109pub(crate) async fn stop(services: Arc<Services>) -> Result {
110 debug!("Shutting down...");
111
112 #[cfg(all(feature = "systemd", target_os = "linux"))]
113 notify_systemd_shutdown(&services.server);
114
115 services.stop().await;
118
119 debug!("Cleaning up...");
123 let db = Arc::downgrade(&services.db);
124 if let Err(services) = Arc::try_unwrap(services) {
125 debug_error!(
126 "{} dangling references to Services after shutdown",
127 Arc::strong_count(&services)
128 );
129 }
130
131 if Weak::strong_count(&db) > 0 {
132 debug_error!(
133 "{} dangling references to Database after shutdown",
134 Weak::strong_count(&db)
135 );
136 }
137
138 info!("Shutdown complete.");
139 Ok(())
140}
141
142#[cfg(all(feature = "systemd", target_os = "linux"))]
143fn notify_systemd_shutdown(server: &Server) {
144 if server.is_restarting() {
149 let monotonic = NotifyState::monotonic_usec_now().expect("failed to get monotonic time");
150
151 notify(&[NotifyState::Reloading, monotonic, NotifyState::Watchdog])
152 .expect("failed to notify systemd of reloading state");
153
154 return;
155 }
156
157 unsafe { notify_and_unset_env(&[NotifyState::Stopping]) }
161 .expect("failed to notify systemd of stopping state");
162}
163
164#[tracing::instrument(skip_all)]
165async fn signal(server: Arc<Server>, handle: ServerHandle) {
166 server.until_shutdown().await;
167 handle_shutdown(&server, &handle);
168}
169
170fn handle_shutdown(server: &Arc<Server>, handle: &ServerHandle) {
171 let timeout = server.config.client_shutdown_timeout;
172 let timeout = Duration::from_secs(timeout);
173 debug!(
174 ?timeout,
175 handle_active = ?server.metrics.requests_handle_active.load(Ordering::Relaxed),
176 "Notifying for graceful shutdown"
177 );
178
179 handle.graceful_shutdown(Some(timeout));
180}
181
182fn handle_services_finish(
183 server: &Arc<Server>,
184 result: Result,
185 listener: Option<Result>,
186) -> Result {
187 debug!("Service manager finished: {result:?}");
188
189 if server.is_running()
190 && let Err(e) = server.shutdown()
191 {
192 error!("Failed to send shutdown signal: {e}");
193 }
194
195 if let Some(Err(e)) = listener {
196 error!("Client listener task finished with error: {e}");
197 }
198
199 result
200}
201
202#[cfg(all(feature = "systemd", target_os = "linux"))]
203#[expect(clippy::infinite_loop)]
204async fn start_systemd_watchdog() {
205 use tokio::time::MissedTickBehavior;
206
207 let Some(watchdog) = watchdog_enabled() else {
208 return;
209 };
210
211 let watchdog_usec = u64::try_from(watchdog.as_micros()).unwrap_or(u64::MAX);
212 let interval_usec = (watchdog_usec / 2).max(1);
213 let interval = Duration::from_micros(interval_usec);
214
215 let mut ticker = tokio::time::interval(interval);
216 ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
217 loop {
218 ticker.tick().await;
219
220 if let Err(e) = notify(&[NotifyState::Watchdog]) {
221 error!(%e, "failed to notify systemd watchdog state");
222 }
223 }
224}
225
226#[cfg(all(feature = "systemd", target_os = "linux"))]
227#[expect(clippy::infinite_loop)]
228async fn extend_systemd_startup() {
229 use std::env;
230
231 use tokio::time::MissedTickBehavior;
232
233 const INTERVAL: Duration = Duration::from_secs(15);
234
235 if env::var_os("NOTIFY_SOCKET").is_none() {
238 return;
239 }
240
241 let extend_usec = u32::try_from(INTERVAL.as_micros())
242 .unwrap_or(u32::MAX)
243 .saturating_mul(2);
244
245 let mut ticker = tokio::time::interval(INTERVAL);
246 ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
247 loop {
248 ticker.tick().await;
249
250 if let Err(e) = notify(&[NotifyState::ExtendTimeoutUsec(extend_usec)]) {
251 error!(%e, "failed to extend systemd startup timeout");
252 }
253 }
254}