torrust_tracker/servers/udp/server/
states.rs

1use std::fmt::Debug;
2use std::net::SocketAddr;
3use std::sync::Arc;
4
5use derive_more::derive::Display;
6use derive_more::Constructor;
7use tokio::task::JoinHandle;
8use tracing::{instrument, Level};
9
10use super::spawner::Spawner;
11use super::{Server, UdpError};
12use crate::bootstrap::jobs::Started;
13use crate::core::Tracker;
14use crate::servers::registar::{ServiceRegistration, ServiceRegistrationForm};
15use crate::servers::signals::Halted;
16use crate::servers::udp::server::launcher::Launcher;
17use crate::servers::udp::UDP_TRACKER_LOG_TARGET;
18
19/// A UDP server instance controller with no UDP instance running.
20#[allow(clippy::module_name_repetitions)]
21pub type StoppedUdpServer = Server<Stopped>;
22
23/// A UDP server instance controller with a running UDP instance.
24#[allow(clippy::module_name_repetitions)]
25pub type RunningUdpServer = Server<Running>;
26
27/// A stopped UDP server state.
28#[derive(Debug, Display)]
29#[display("Stopped: {spawner}")]
30pub struct Stopped {
31    pub spawner: Spawner,
32}
33
34/// A running UDP server state.
35#[derive(Debug, Display, Constructor)]
36#[display("Running (with local address): {local_addr}")]
37pub struct Running {
38    /// The address where the server is bound.
39    pub local_addr: SocketAddr,
40    pub halt_task: tokio::sync::oneshot::Sender<Halted>,
41    pub task: JoinHandle<Spawner>,
42}
43
44impl Server<Stopped> {
45    /// Creates a new `UdpServer` instance in `stopped`state.
46    #[must_use]
47    pub fn new(spawner: Spawner) -> Self {
48        Self {
49            state: Stopped { spawner },
50        }
51    }
52
53    /// It starts the server and returns a `UdpServer` controller in `running`
54    /// state.
55    ///
56    /// # Errors
57    ///
58    /// Will return `Err` if UDP can't bind to given bind address.
59    ///
60    /// # Panics
61    ///
62    /// It panics if unable to receive the bound socket address from service.
63    ///
64    #[instrument(skip(self, tracker, form), err, ret(Display, level = Level::INFO))]
65    pub async fn start(self, tracker: Arc<Tracker>, form: ServiceRegistrationForm) -> Result<Server<Running>, std::io::Error> {
66        let (tx_start, rx_start) = tokio::sync::oneshot::channel::<Started>();
67        let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::<Halted>();
68
69        assert!(!tx_halt.is_closed(), "Halt channel for UDP tracker should be open");
70
71        // May need to wrap in a task to about a tokio bug.
72        let task = self.state.spawner.spawn_launcher(tracker, tx_start, rx_halt);
73
74        let local_addr = rx_start.await.expect("it should be able to start the service").address;
75
76        form.send(ServiceRegistration::new(local_addr, Launcher::check))
77            .expect("it should be able to send service registration");
78
79        let running_udp_server: Server<Running> = Server {
80            state: Running {
81                local_addr,
82                halt_task: tx_halt,
83                task,
84            },
85        };
86
87        let local_addr = format!("udp://{local_addr}");
88        tracing::trace!(target: UDP_TRACKER_LOG_TARGET, local_addr, "UdpServer<Stopped>::start (running)");
89
90        Ok(running_udp_server)
91    }
92}
93
94impl Server<Running> {
95    /// It stops the server and returns a `UdpServer` controller in `stopped`
96    /// state.
97    ///     
98    /// # Errors
99    ///
100    /// Will return `Err` if the oneshot channel to send the stop signal
101    /// has already been called once.
102    ///
103    /// # Panics
104    ///
105    /// It panics if unable to shutdown service.
106    #[instrument(skip(self), err, ret(Display, level = Level::INFO))]
107    pub async fn stop(self) -> Result<Server<Stopped>, UdpError> {
108        self.state
109            .halt_task
110            .send(Halted::Normal)
111            .map_err(|e| UdpError::FailedToStartOrStopServer(e.to_string()))?;
112
113        let launcher = self.state.task.await.expect("it should shutdown service");
114
115        let stopped_api_server: Server<Stopped> = Server {
116            state: Stopped { spawner: launcher },
117        };
118
119        Ok(stopped_api_server)
120    }
121}