Skip to main content

tuwunel_core/
server.rs

1//! Tracks server lifecycle state and its runtime handle.
2//!
3//! The server coordinates reload, restart, and shutdown notifications. Shared
4//! services use its state to stop work promptly during teardown.
5
6#[cfg(test)]
7mod tests;
8
9use std::{
10	sync::{
11		Arc,
12		atomic::{AtomicBool, Ordering},
13	},
14	time::SystemTime,
15};
16
17use ruma::OwnedServerName;
18use tokio::{runtime, sync::broadcast};
19
20use crate::{Err, Result, config, config::Config, log::Logging, metrics::Metrics};
21
22/// Server runtime state; public portion
23pub struct Server {
24	/// Configured name of server. This is the same as the one in the config
25	/// but developers can (and should) reference this string instead.
26	pub name: OwnedServerName,
27
28	/// Server-wide configuration instance
29	pub config: config::Manager,
30
31	/// Where the configuration came from, replayed on reload.
32	pub config_sources: config::Sources,
33
34	/// Timestamp server was started; used for uptime.
35	pub started: SystemTime,
36
37	/// Reload/shutdown pending indicator; server is shutting down. This is an
38	/// observable used on shutdown and should not be modified.
39	pub stopping: AtomicBool,
40
41	/// Reload/shutdown desired indicator; when false, shutdown is desired. This
42	/// is an observable used on shutdown and modifying is not recommended.
43	pub reloading: AtomicBool,
44
45	/// Restart desired; when true, restart it desired after shutdown.
46	pub restarting: AtomicBool,
47
48	/// Set when a backup restore is claimed, which is before it runs and is not
49	/// undone if it fails, so a database reopened later in the same process
50	/// does not restore a second time. Clearing this re-arms a destructive
51	/// operation and is never correct; claim it instead.
52	pub backup_restored: AtomicBool,
53
54	/// Handle to the runtime
55	pub runtime: Option<runtime::Handle>,
56
57	/// Reload/shutdown signal
58	pub signal: broadcast::Sender<&'static str>,
59
60	/// Logging subsystem state
61	pub log: Logging,
62
63	/// Metrics subsystem state
64	pub metrics: Arc<Metrics>,
65}
66
67impl Server {
68	#[must_use]
69	/// Creates shared server lifecycle state.
70	///
71	/// The initial configuration, source list, logging state, and metrics
72	/// become available to all services. A supplied runtime handle enables
73	/// task spawning.
74	pub fn new(
75		config: Config,
76		config_sources: config::Sources,
77		runtime: Option<&runtime::Handle>,
78		log: Logging,
79		metrics: Arc<Metrics>,
80	) -> Self {
81		Self {
82			name: config.server_name.clone(),
83			config: config::Manager::new(config),
84			config_sources,
85			started: SystemTime::now(),
86			stopping: AtomicBool::new(false),
87			reloading: AtomicBool::new(false),
88			restarting: AtomicBool::new(false),
89			backup_restored: AtomicBool::new(false),
90			runtime: runtime.cloned(),
91			signal: broadcast::channel::<&'static str>(1).0,
92			log,
93			metrics,
94		}
95	}
96
97	/// Requests a dynamic module reload.
98	///
99	/// The request marks the server as reloading and stopping before
100	/// broadcasting `SIGINT`. Concurrent reload or shutdown requests are
101	/// rejected.
102	pub fn reload(&self) -> Result {
103		if cfg!(any(not(tuwunel_mods), not(feature = "tuwunel_mods"))) {
104			return Err!("Reloading not enabled");
105		}
106
107		if self.reloading.swap(true, Ordering::AcqRel) {
108			return Err!("Reloading already in progress");
109		}
110
111		if self.stopping.swap(true, Ordering::AcqRel) {
112			return Err!("Shutdown already in progress");
113		}
114
115		self.signal("SIGINT").inspect_err(|_| {
116			self.stopping.store(false, Ordering::Release);
117			self.reloading.store(false, Ordering::Release);
118		})
119	}
120
121	/// Requests a process restart through the normal shutdown path.
122	///
123	/// The restarting flag is claimed once before shutdown begins. A rejected
124	/// shutdown clears that flag so a later request may retry.
125	pub fn restart(&self) -> Result {
126		if self.restarting.swap(true, Ordering::AcqRel) {
127			return Err!("Restart already in progress");
128		}
129
130		self.shutdown().inspect_err(|_| {
131			self.restarting.store(false, Ordering::Release);
132		})
133	}
134
135	/// Requests an orderly server shutdown.
136	///
137	/// The stopping flag is claimed once before broadcasting `SIGTERM`. A
138	/// second request is rejected while shutdown remains in progress.
139	pub fn shutdown(&self) -> Result {
140		if self.stopping.swap(true, Ordering::AcqRel) {
141			return Err!("Shutdown already in progress");
142		}
143
144		self.signal("SIGTERM").inspect_err(|_| {
145			self.stopping.store(false, Ordering::Release);
146		})
147	}
148
149	/// Claims the one-shot backup restore, reporting whether this caller is the
150	/// one to perform it.
151	#[inline]
152	pub fn claim_backup_restore(&self) -> bool {
153		!self.backup_restored.swap(true, Ordering::AcqRel)
154	}
155
156	/// Broadcasts a process-signal name to lifecycle subscribers.
157	///
158	/// Delivery is best effort because subscribers may not yet be listening.
159	/// The method therefore succeeds even when the channel has no receivers.
160	pub fn signal(&self, sig: &'static str) -> Result {
161		self.signal.send(sig).ok();
162		Ok(())
163	}
164
165	#[inline]
166	/// Waits until the server enters its stopping state.
167	///
168	/// Lifecycle notifications wake the loop so it can recheck the shared
169	/// state. Calling it after shutdown has begun returns immediately.
170	pub async fn until_shutdown(self: &Arc<Self>) {
171		let mut signal = self.signal.subscribe();
172		while self.is_running() {
173			signal.recv().await.ok();
174		}
175	}
176
177	#[inline]
178	/// Returns the runtime handle supplied during server construction.
179	///
180	/// Services use this handle to spawn work on the embedding runtime. The
181	/// handle is borrowed for the lifetime of the server.
182	///
183	/// # Panics
184	///
185	/// Panics when the server was constructed without a runtime handle.
186	pub fn runtime(&self) -> &runtime::Handle {
187		self.runtime
188			.as_ref()
189			.expect("runtime handle available in Server")
190	}
191
192	#[inline]
193	/// Rejects new work after shutdown begins.
194	///
195	/// A running server returns success. A stopping server returns an
196	/// interrupted I/O error wrapped in the shared error type.
197	pub fn check_running(&self) -> Result {
198		use std::{io, io::ErrorKind::Interrupted};
199
200		self.is_running()
201			.then_some(())
202			.ok_or_else(|| io::Error::new(Interrupted, "Server shutting down"))
203			.map_err(Into::into)
204	}
205
206	#[inline]
207	/// Reports whether the server still accepts work.
208	///
209	/// Running is the inverse of the stopping state. Reload and restart
210	/// requests also transition the server through stopping.
211	pub fn is_running(&self) -> bool { !self.is_stopping() }
212
213	#[inline]
214	/// Reports whether shutdown has begun.
215	///
216	/// The flag is set by shutdown and reload transitions. Reads are relaxed
217	/// because callers use it as a lifecycle observation rather than a
218	/// synchronization edge.
219	pub fn is_stopping(&self) -> bool { self.stopping.load(Ordering::Relaxed) }
220
221	#[inline]
222	/// Reports whether a dynamic module reload is in progress.
223	///
224	/// Reload claims the flag before checking the stopping state.
225	/// Signal-delivery failure clears it, while rejection by an existing
226	/// shutdown can leave it set.
227	pub fn is_reloading(&self) -> bool { self.reloading.load(Ordering::Relaxed) }
228
229	#[inline]
230	/// Reports whether a process restart is in progress.
231	///
232	/// Restart claims the flag before requesting shutdown. Failed shutdown
233	/// initiation clears it for a later attempt.
234	pub fn is_restarting(&self) -> bool { self.restarting.load(Ordering::Relaxed) }
235
236	#[inline]
237	/// Reports whether a name matches the configured local server name.
238	///
239	/// The comparison uses the active configuration snapshot. It performs an
240	/// exact, case-sensitive string comparison.
241	pub fn is_ours(&self, name: &str) -> bool { name == self.config.server_name }
242}