Skip to main content

ntex_server/
wrk.rs

1use std::sync::atomic::{AtomicBool, Ordering};
2use std::task::{Context, Poll, ready};
3use std::{cmp, future::Future, future::poll_fn, hash, pin::Pin, sync::Arc};
4
5use async_channel::{Receiver, Sender, TrySendError, unbounded};
6use atomic_waker::AtomicWaker;
7use core_affinity::CoreId;
8
9use ntex_rt::{Arbiter, spawn};
10use ntex_service::Pipeline;
11use ntex_util::future::{Either, Stream, select, stream_recv};
12use ntex_util::time::{Millis, sleep, timeout_checked};
13
14use crate::ServerConfiguration;
15
16const STOP_TIMEOUT: Millis = Millis(3000);
17
18#[derive(Debug)]
19/// Shutdown worker command.
20struct Shutdown {
21    timeout: Millis,
22    result: oneshot::Sender<bool>,
23}
24
25#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
26/// Worker status.
27pub enum WorkerStatus {
28    Available,
29    #[default]
30    Unavailable,
31    Failed,
32}
33
34#[derive(Debug)]
35/// Server worker.
36///
37/// Worker accepts message via unbounded channel and starts processing.
38pub struct Worker<T> {
39    name: String,
40    reqs: Sender<T>,
41    stop: Sender<Shutdown>,
42    avail: WorkerAvailability,
43}
44
45#[derive(Debug)]
46/// Stop worker process.
47///
48/// Stop future resolves when worker completes processing
49/// incoming items and stop arbiter
50pub struct WorkerStop(oneshot::AsyncReceiver<bool>);
51
52impl<T> Worker<T> {
53    /// Start worker.
54    pub fn start<F>(name: String, cfg: F, cid: Option<CoreId>) -> Worker<T>
55    where
56        T: Send + 'static,
57        F: ServerConfiguration<Item = T>,
58    {
59        let (reqs, r_rx) = unbounded();
60        let (stop, s_rx) = unbounded();
61        let (avail, a_tx) = WorkerAvailability::create();
62        let n = name.clone();
63        let inner = avail.inner.clone();
64
65        let worker = Worker {
66            reqs,
67            avail,
68            stop,
69            name: name.clone(),
70        };
71
72        Arbiter::with_name(name)
73            .on_stop(move || {
74                inner.failed.store(true, Ordering::Release);
75                inner.updated.store(true, Ordering::Release);
76                inner.available.store(false, Ordering::Release);
77                inner.waker.wake();
78            })
79            .handle()
80            .spawn(async move {
81                log::info!("Starting worker {n:?}");
82                if let Some(cid) = cid
83                    && core_affinity::set_for_current(cid)
84                {
85                    log::info!("Set affinity to {cid:?} for worker {n:?}");
86                }
87
88                spawn(async move {
89                    match ServiceRunner::create(&n, cfg, r_rx, s_rx, a_tx).await {
90                        Ok(wrk) => {
91                            log::debug!("Server instance has been created in {n:?}");
92                            wrk.run().await;
93                        }
94                        Err(()) => {
95                            log::error!("Cannot start worker {n:?}");
96                        }
97                    }
98                    Arbiter::current().stop();
99                });
100            });
101
102        worker
103    }
104
105    /// Worker name
106    pub fn name(&self) -> &str {
107        &self.name
108    }
109
110    #[inline]
111    /// Sends a message to the worker.
112    ///
113    /// Returns `Ok` if the worker accepts the message.
114    /// Otherwise, returns the message as `Err`.
115    pub fn send(&self, msg: T) -> Result<(), T> {
116        self.reqs.try_send(msg).map_err(TrySendError::into_inner)
117    }
118
119    /// Check worker status.
120    pub fn status(&self) -> WorkerStatus {
121        if self.avail.failed() {
122            WorkerStatus::Failed
123        } else if self.avail.available() {
124            WorkerStatus::Available
125        } else {
126            WorkerStatus::Unavailable
127        }
128    }
129
130    /// Wait for worker status updates.
131    pub async fn wait_for_status(&mut self) -> WorkerStatus {
132        if self.avail.failed() {
133            WorkerStatus::Failed
134        } else {
135            self.avail.wait_for_update().await;
136            self.status()
137        }
138    }
139
140    /// Stop the worker.
141    ///
142    /// If the timeout is zero, forcefully shut down the worker.
143    pub fn stop(&self, timeout: Millis) -> WorkerStop {
144        let (result, rx) = oneshot::async_channel();
145        let _ = self.stop.try_send(Shutdown { timeout, result });
146        WorkerStop(rx)
147    }
148}
149
150impl<T> Eq for Worker<T> {}
151
152impl<T> PartialEq for Worker<T> {
153    fn eq(&self, other: &Worker<T>) -> bool {
154        self.name == other.name
155    }
156}
157
158impl<T> cmp::Ord for Worker<T> {
159    fn cmp(&self, other: &Self) -> cmp::Ordering {
160        self.name.cmp(&other.name)
161    }
162}
163
164impl<T> cmp::PartialOrd for Worker<T> {
165    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
166        Some(self.cmp(other))
167    }
168}
169
170impl<T> hash::Hash for Worker<T> {
171    fn hash<H: hash::Hasher>(&self, state: &mut H) {
172        self.name.hash(state);
173    }
174}
175
176impl<T> Clone for Worker<T> {
177    fn clone(&self) -> Self {
178        Worker {
179            name: self.name.clone(),
180            reqs: self.reqs.clone(),
181            stop: self.stop.clone(),
182            avail: self.avail.clone(),
183        }
184    }
185}
186
187impl Future for WorkerStop {
188    type Output = bool;
189
190    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
191        match ready!(Pin::new(&mut self.0).poll(cx)) {
192            Ok(res) => Poll::Ready(res),
193            Err(_) => Poll::Ready(true),
194        }
195    }
196}
197
198#[derive(Debug, Clone)]
199struct WorkerAvailability {
200    inner: Arc<Inner>,
201}
202
203#[derive(Debug, Clone)]
204struct WorkerAvailabilityTx {
205    inner: Arc<Inner>,
206}
207
208#[derive(Debug)]
209struct Inner {
210    waker: AtomicWaker,
211    updated: AtomicBool,
212    available: AtomicBool,
213    failed: AtomicBool,
214}
215
216impl WorkerAvailability {
217    fn create() -> (Self, WorkerAvailabilityTx) {
218        let inner = Arc::new(Inner {
219            waker: AtomicWaker::new(),
220            updated: AtomicBool::new(false),
221            available: AtomicBool::new(false),
222            failed: AtomicBool::new(false),
223        });
224
225        let avail = WorkerAvailability {
226            inner: inner.clone(),
227        };
228        let avail_tx = WorkerAvailabilityTx { inner };
229        (avail, avail_tx)
230    }
231
232    fn failed(&self) -> bool {
233        self.inner.failed.load(Ordering::Acquire)
234    }
235
236    fn available(&self) -> bool {
237        self.inner.available.load(Ordering::Acquire)
238    }
239
240    async fn wait_for_update(&self) {
241        poll_fn(|cx| {
242            self.inner.waker.register(cx.waker());
243            if self.inner.updated.swap(false, Ordering::AcqRel) {
244                Poll::Ready(())
245            } else {
246                Poll::Pending
247            }
248        })
249        .await;
250    }
251}
252
253impl WorkerAvailabilityTx {
254    fn set(&self, val: bool) {
255        let old = self.inner.available.swap(val, Ordering::Release);
256        if old != val {
257            self.inner.updated.store(true, Ordering::Release);
258            self.inner.waker.wake();
259        }
260    }
261}
262
263impl Drop for WorkerAvailabilityTx {
264    fn drop(&mut self) {
265        self.inner.failed.store(true, Ordering::Release);
266        self.inner.updated.store(true, Ordering::Release);
267        self.inner.available.store(false, Ordering::Release);
268        self.inner.waker.wake();
269    }
270}
271
272/// Service runner.
273///
274/// The runner receives messages through an unbounded channel and processes them.
275struct ServiceRunner<F: ServerConfiguration<Item = Req>, Req> {
276    name: String,
277    factory: F,
278    svc: Pipeline<Req, (), ()>,
279    reqs: Receiver<Req>,
280    stop: Pin<Box<dyn Stream<Item = Shutdown>>>,
281    availability: WorkerAvailabilityTx,
282}
283
284impl<F, Req> ServiceRunner<F, Req>
285where
286    Req: Send + 'static,
287    F: ServerConfiguration<Item = Req> + 'static,
288{
289    async fn create(
290        name: &str,
291        factory: F,
292        reqs: Receiver<Req>,
293        stop: Receiver<Shutdown>,
294        availability: WorkerAvailabilityTx,
295    ) -> Result<Self, ()> {
296        availability.set(false);
297        let mut stop = Box::pin(stop);
298
299        let svc = match select(factory.create(), stream_recv(&mut stop)).await {
300            Either::Left(Ok(svc)) => Pipeline::with((), svc),
301            Either::Right(Some(Shutdown { result, .. })) => {
302                log::trace!("Shutdown uninitialized worker");
303                let _ = result.send(false);
304                return Err(());
305            }
306            Either::Left(Err(_)) | Either::Right(None) => return Err(()),
307        };
308        availability.set(true);
309
310        Ok(ServiceRunner {
311            factory,
312            svc,
313            reqs,
314            stop,
315            availability,
316            name: name.to_string(),
317        })
318    }
319
320    async fn run(mut self) {
321        loop {
322            let mut recv = std::pin::pin!(self.reqs.recv());
323            let fut = poll_fn(|cx| {
324                match self.svc.poll_ready(cx) {
325                    Poll::Ready(Ok(())) => {
326                        self.availability.set(true);
327                    }
328                    Poll::Ready(Err(err)) => {
329                        self.availability.set(false);
330                        return Poll::Ready(Err(err));
331                    }
332                    Poll::Pending => {
333                        self.availability.set(false);
334                        return Poll::Pending;
335                    }
336                }
337
338                if let Ok(item) = ready!(recv.as_mut().poll(cx)) {
339                    Poll::Ready(Ok(Some(item)))
340                } else {
341                    log::error!("Server is gone");
342                    Poll::Ready(Ok(None))
343                }
344            });
345
346            match select(fut, stream_recv(&mut self.stop)).await {
347                Either::Left(Ok(Some(item))) => {
348                    // got item
349                    let _ = self.svc.call(item).await;
350                    continue;
351                }
352                Either::Left(Err(())) => {
353                    // re-create service
354                    ntex_rt::spawn(async move {
355                        self.svc.shutdown().await;
356                    });
357                }
358                Either::Right(Some(Shutdown { timeout, result })) => {
359                    log::info!("Shutting down {:?} worker gracefuly", self.name);
360                    self.availability.set(false);
361
362                    let timeout = if timeout.is_zero() { STOP_TIMEOUT } else { timeout };
363                    self.stop(timeout, Some(result)).await;
364                    return;
365                }
366                Either::Left(Ok(None)) | Either::Right(None) => {
367                    log::info!("Shutting down {:?} worker", self.name);
368                    self.availability.set(false);
369                    self.stop(STOP_TIMEOUT, None).await;
370                    return;
371                }
372            }
373
374            // re-create service
375            loop {
376                match select(self.factory.create(), stream_recv(&mut self.stop)).await {
377                    Either::Left(Ok(service)) => {
378                        self.svc = Pipeline::with((), service);
379                        break;
380                    }
381                    Either::Left(Err(_)) => sleep(Millis::ONE_SEC).await,
382                    Either::Right(_) => return,
383                }
384            }
385        }
386    }
387
388    async fn stop(&self, timeout: Millis, result: Option<oneshot::Sender<bool>>) {
389        let res = timeout_checked(timeout, self.svc.shutdown()).await;
390        if let Some(result) = result {
391            let _ = result.send(res.is_ok());
392        }
393
394        log::info!("Worker {:?} has been stopped", self.name);
395    }
396}