Skip to main content

ntex_server/net/
service.rs

1use std::{fmt, io, sync::Arc};
2
3use ntex_io::{IoConfig, IoMetricsGroup};
4use ntex_service::{BindCtx, Ctx, Service, cfg::SharedCfg};
5use ntex_util::{HashMap, future::join_all, services::Counter, services::counter::CounterGuard};
6
7use crate::{ServerAppConfig, ServerConfiguration, affinity, metrics::WorkerSlot};
8
9use super::accept::{AcceptNotify, AcceptorCommand};
10use super::factory::{FactoryServiceType, NetService};
11use super::{MAX_CONNS_COUNTER, Token, socket::Connection, socket::Stream};
12
13/// Worker configuration for network servers.
14///
15/// Created by [`ServerBuilder::run`](super::ServerBuilder::run). Each worker
16/// creates its application state and the services for all registered
17/// listeners.
18pub struct StreamServer<Cfg> {
19    accept: AcceptNotify,
20    state: Arc<Cfg>,
21    services: Vec<FactoryServiceType<Cfg>>,
22}
23
24impl<Cfg: ServerAppConfig> StreamServer<Cfg> {
25    pub(crate) fn new(
26        accept: AcceptNotify,
27        state: Arc<Cfg>,
28        services: Vec<FactoryServiceType<Cfg>>,
29    ) -> Self {
30        Self {
31            accept,
32            state,
33            services,
34        }
35    }
36}
37
38impl<Cfg> fmt::Debug for StreamServer<Cfg> {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.debug_struct("StreamServer")
41            .field("services", &self.services.len())
42            .finish()
43    }
44}
45
46/// Worker service factory.
47impl<Cfg: ServerAppConfig> ServerConfiguration for StreamServer<Cfg> {
48    type Item = Connection;
49    type Service = StreamService;
50
51    /// Create service for handling connections
52    async fn create(&self) -> io::Result<Self::Service> {
53        // construct configuration
54        let cfg = self.state.create().await?;
55
56        // construct services
57        let mut tokens = HashMap::default();
58        let mut services = Vec::new();
59        let group = IoMetricsGroup::current();
60
61        for info in &self.services {
62            for (svc, _, svc_tokens) in info.create(cfg.clone()).await.map_err(io::Error::other)? {
63                services.push(svc);
64                let idx = services.len() - 1;
65                for (token, cfg) in &svc_tokens {
66                    // listener connections have their own metrics
67                    group.register(&cfg.get::<IoConfig>());
68                    tokens.insert(*token, (idx, cfg.clone()));
69                }
70            }
71        }
72
73        Ok(StreamService {
74            services,
75            tokens,
76            conns: MAX_CONNS_COUNTER.with(Clone::clone),
77            metrics: WorkerSlot::current(),
78        })
79    }
80
81    /// Pause the server.
82    fn pause(&self) {
83        self.accept.send(AcceptorCommand::Pause);
84    }
85
86    /// Resume the server.
87    fn resume(&self) {
88        self.accept.send(AcceptorCommand::Resume);
89    }
90
91    /// Terminate the server.
92    fn terminate(&self) {
93        self.accept.send(AcceptorCommand::Terminate);
94    }
95
96    /// Stop the server.
97    async fn stop(&self) {
98        let (tx, rx) = oneshot::channel();
99        self.accept.send(AcceptorCommand::Stop(tx));
100        let _ = rx.await;
101    }
102
103    fn incoming_cpu(&self, con: &Connection) -> Option<usize> {
104        match con.io {
105            Stream::Tcp(ref stream) => affinity::incoming_cpu(stream),
106            #[cfg(unix)]
107            Stream::Uds(_) => None,
108        }
109    }
110}
111
112impl<Cfg: ServerAppConfig> Clone for StreamServer<Cfg> {
113    fn clone(&self) -> Self {
114        Self {
115            state: self.state.clone(),
116            accept: self.accept.clone(),
117            services: self.services.iter().map(|s| s.clo()).collect(),
118        }
119    }
120}
121
122/// Per-worker service that dispatches connections to listener services.
123pub struct StreamService {
124    tokens: HashMap<Token, (usize, SharedCfg)>,
125    services: Vec<Box<dyn NetService>>,
126    conns: Counter,
127    metrics: Option<Arc<WorkerSlot>>,
128}
129
130/// Open connection, released on drop.
131pub(crate) struct ConnGuard {
132    _guard: CounterGuard,
133    metrics: Option<Arc<WorkerSlot>>,
134}
135
136impl ConnGuard {
137    fn new(guard: CounterGuard, metrics: Option<Arc<WorkerSlot>>) -> Self {
138        if let Some(ref m) = metrics {
139            m.conn_opened();
140        }
141        Self {
142            _guard: guard,
143            metrics,
144        }
145    }
146}
147
148impl Drop for ConnGuard {
149    fn drop(&mut self) {
150        if let Some(ref m) = self.metrics {
151            m.conn_closed();
152        }
153    }
154}
155
156impl fmt::Debug for StreamService {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        f.debug_struct("StreamService")
159            .field("tokens", &self.tokens)
160            .field("conns", &self.conns)
161            .finish()
162    }
163}
164
165impl Service<(), Connection> for StreamService {
166    type Res = ();
167    type Error = ();
168
169    fn bind(&self, ctx: BindCtx<'_>) {
170        self.conns.bind(&ctx);
171        for service in &self.services {
172            service.bind(&ctx);
173        }
174    }
175
176    async fn ready(&self, _: Ctx<'_, Self, ()>) -> Result<(), Self::Error> {
177        if !self.conns.is_available() {
178            self.conns.available().await;
179        }
180        for (idx, svc) in self.services.iter().enumerate() {
181            if svc.ready().await.is_err() {
182                for (idx_, cfg) in self.tokens.values() {
183                    if idx == *idx_ {
184                        log::error!("{}: Service readiness has failed", cfg.tag());
185                        break;
186                    }
187                }
188                return Err(());
189            }
190        }
191
192        Ok(())
193    }
194
195    async fn shutdown(&self, _: Ctx<'_, Self, ()>) {
196        let _ = join_all(self.services.iter().map(|s| s.shutdown())).await;
197        log::info!(
198            "Worker service shutdown, {} connections",
199            super::num_connections()
200        );
201    }
202
203    #[allow(clippy::unused_async_trait_impl)]
204    async fn call(&self, con: Connection, _: Ctx<'_, Self, ()>) -> Result<(), ()> {
205        if let Some((idx, cfg)) = self.tokens.get(&con.token) {
206            let stream = con.io.convert(cfg.clone()).map_err(|e| {
207                log::error!("Cannot convert to an async io stream: {e}");
208            })?;
209
210            let guard = ConnGuard::new(self.conns.get(), self.metrics.clone());
211            self.services[*idx].call(stream, guard);
212            Ok(())
213        } else {
214            log::error!("Cannot get handler service for connection: {con:?}");
215            Err(())
216        }
217    }
218}