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