Skip to main content

ntex_server/
wrk.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2use std::sync::{Arc, OnceLock};
3use std::task::{Context, Poll, ready};
4use std::{cmp, future::Future, future::poll_fn, hash, pin::Pin};
5
6use async_channel::{Receiver, Sender, TrySendError, unbounded};
7use event_listener::Event;
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, affinity::Placement, metrics::WorkerSlot};
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 has stopped.
34    ///
35    /// Reported when the worker panics, its service cannot be created, or
36    /// it finishes a regular stop.
37    Failed,
38}
39
40#[derive(Debug)]
41/// Server worker.
42///
43/// A worker runs one service instance on its own arbiter thread. It receives
44/// items through an unbounded channel. Clones refer to the same worker.
45pub struct Worker<T> {
46    name: String,
47    reqs: Sender<T>,
48    /// Takes back items the worker has not received yet.
49    queue: Receiver<T>,
50    stop: Sender<Shutdown>,
51    avail: WorkerAvailability,
52    arbiter: Arbiter,
53    metrics: Option<Arc<WorkerSlot>>,
54    /// Set once the placement is applied.
55    cpus: Arc<OnceLock<Box<[usize]>>>,
56}
57
58#[derive(Debug)]
59/// Future returned when stopping a worker.
60///
61/// Resolves to `true` if the worker's service shut down within the timeout,
62/// or if the worker was already gone. Resolves to `false` if the timeout
63/// expired, or if the worker was stopped before its service was created.
64pub struct WorkerStop(oneshot::AsyncReceiver<bool>);
65
66impl<T> Worker<T> {
67    /// Starts a worker on a new arbiter thread.
68    ///
69    /// Returns immediately. The worker reports
70    /// [`Unavailable`](WorkerStatus::Unavailable) until its service is
71    /// created. If `cpu` is set, the worker thread is pinned to the logical
72    /// CPU with that index. On Windows, CPUs are numbered across processor
73    /// groups, the CPUs of group 1 follow the CPUs of group 0. Pinning is
74    /// supported on Windows, Linux and Android.
75    pub fn start<F>(name: String, cfg: F, cpu: Option<usize>) -> Worker<T>
76    where
77        T: Send + 'static,
78        F: ServerConfiguration<Item = T>,
79    {
80        Self::start_with(name, cfg, cpu.map(Placement::cpu), None)
81    }
82
83    pub(crate) fn start_with<F>(
84        name: String,
85        cfg: F,
86        placement: Option<Placement>,
87        metrics: Option<Arc<WorkerSlot>>,
88    ) -> Worker<T>
89    where
90        T: Send + 'static,
91        F: ServerConfiguration<Item = T>,
92    {
93        let (reqs, r_rx) = unbounded();
94        let (stop, s_rx) = unbounded();
95        let (avail, a_tx) = WorkerAvailability::create();
96        let n = name.clone();
97        let inner = avail.inner.clone();
98        let cpus = Arc::new(OnceLock::new());
99
100        let n2 = name.clone();
101        let cpus2 = cpus.clone();
102        let init = move || {
103            if let Some(placement) = placement {
104                if placement.apply() {
105                    log::info!("Set affinity to {placement} for worker {n2:?}");
106                    // the worker is not available before init completes,
107                    // the balancer reads the CPUs once it is
108                    let _ = cpus2.set(placement.cpus().into());
109                } else {
110                    log::warn!("Cannot set affinity to {placement} for worker {n2:?}");
111                }
112            }
113        };
114
115        // placement is applied before the runtime is created, so its memory
116        // is allocated on the NUMA node of the worker's CPU
117        let arbiter =
118            Arbiter::with_init(name.clone(), init).on_stop(move || inner.set_failed(true));
119        if let Some(ref m) = metrics {
120            m.set_arbiter(arbiter.clone());
121        }
122        let worker = Worker {
123            reqs,
124            queue: r_rx.clone(),
125            avail,
126            stop,
127            name,
128            arbiter: arbiter.clone(),
129            metrics: metrics.clone(),
130            cpus,
131        };
132        let reqs = Queue(r_rx);
133
134        arbiter.handle().spawn_detached(async move {
135            log::info!("Starting worker {n:?}");
136
137            // the arbiter is stopped even if the service panics, runtimes
138            // that catch task panics keep the arbiter running otherwise
139            WorkerSlot::set_current(metrics.clone());
140            let task = spawn(async move {
141                match ServiceRunner::create(&n, cfg, reqs, s_rx, a_tx, metrics).await {
142                    Ok(wrk) => {
143                        log::debug!("Server instance has been created in {n:?}");
144                        wrk.run().await;
145                    }
146                    Err(()) => {
147                        log::error!("Cannot start worker {n:?}");
148                    }
149                }
150            });
151            ntex_rt::spawn_detached(async move {
152                let _ = task.await;
153                Arbiter::current().stop();
154            });
155        });
156
157        worker
158    }
159
160    /// Returns the worker name.
161    pub fn name(&self) -> &str {
162        &self.name
163    }
164
165    /// Returns the arbiter the worker runs on.
166    pub(crate) fn arbiter(&self) -> &Arbiter {
167        &self.arbiter
168    }
169
170    /// Returns the number of open connections plus queued items.
171    pub(crate) fn load(&self) -> usize {
172        self.metrics.as_ref().map_or(0, |m| m.active()) + self.reqs.len()
173    }
174
175    /// Returns the index of the worker's CPU followed by its SMT siblings,
176    /// empty if the worker is not placed on a CPU or the placement failed.
177    pub(crate) fn cpus(&self) -> &[usize] {
178        self.cpus.get().map_or(&[], |cpus| cpus)
179    }
180
181    #[cfg(test)]
182    pub(crate) fn set_cpus(&self, cpus: &[usize]) {
183        let _ = self.cpus.set(cpus.into());
184    }
185
186    #[inline]
187    /// Sends a message to the worker.
188    ///
189    /// Returns `Ok` if the message was queued. The message is queued even if
190    /// the worker is currently unavailable. Returns the message as `Err` if
191    /// the worker has stopped.
192    pub fn send(&self, msg: T) -> Result<(), T> {
193        self.reqs.try_send(msg).map_err(TrySendError::into_inner)
194    }
195
196    /// Takes the items queued for the worker that it has not received yet.
197    ///
198    /// Takes at most the items queued when called. If `close` is set, the
199    /// queue is closed first, so every queued item is taken and later sends
200    /// fail.
201    pub(crate) fn take_queued(&self, close: bool) -> Vec<T> {
202        if close {
203            self.queue.close();
204        }
205        (0..self.queue.len())
206            .map_while(|_| self.queue.try_recv().ok())
207            .collect()
208    }
209
210    /// Returns the current worker status.
211    pub fn status(&self) -> WorkerStatus {
212        self.avail.inner.status()
213    }
214
215    /// Waits for a status change not yet reported by this handle and
216    /// returns the current status.
217    ///
218    /// Each handle tracks the changes it has reported, so every clone
219    /// observes every change. A clone starts with the reported changes of
220    /// the handle it was cloned from. Several changes between calls are
221    /// reported once. Returns immediately if the worker has failed.
222    pub async fn wait_for_status(&mut self) -> WorkerStatus {
223        self.avail.wait_for_update().await
224    }
225
226    /// Stops the worker.
227    ///
228    /// The worker stops accepting items and shuts down its service, waiting
229    /// up to `timeout`. A zero timeout uses the default of 3 seconds.
230    pub fn stop(&self, timeout: Millis) -> WorkerStop {
231        let (result, rx) = oneshot::async_channel();
232        let _ = self.stop.try_send(Shutdown { timeout, result });
233        WorkerStop(rx)
234    }
235}
236
237impl<T> Eq for Worker<T> {}
238
239impl<T> PartialEq for Worker<T> {
240    fn eq(&self, other: &Worker<T>) -> bool {
241        self.name == other.name
242    }
243}
244
245impl<T> cmp::Ord for Worker<T> {
246    fn cmp(&self, other: &Self) -> cmp::Ordering {
247        self.name.cmp(&other.name)
248    }
249}
250
251impl<T> cmp::PartialOrd for Worker<T> {
252    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
253        Some(self.cmp(other))
254    }
255}
256
257impl<T> hash::Hash for Worker<T> {
258    fn hash<H: hash::Hasher>(&self, state: &mut H) {
259        self.name.hash(state);
260    }
261}
262
263impl<T> Clone for Worker<T> {
264    fn clone(&self) -> Self {
265        Worker {
266            name: self.name.clone(),
267            reqs: self.reqs.clone(),
268            queue: self.queue.clone(),
269            stop: self.stop.clone(),
270            avail: self.avail.clone(),
271            arbiter: self.arbiter.clone(),
272            metrics: self.metrics.clone(),
273            cpus: self.cpus.clone(),
274        }
275    }
276}
277
278impl Future for WorkerStop {
279    type Output = bool;
280
281    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
282        match ready!(Pin::new(&mut self.0).poll(cx)) {
283            Ok(res) => Poll::Ready(res),
284            Err(_) => Poll::Ready(true),
285        }
286    }
287}
288
289#[derive(Debug, Clone)]
290struct WorkerAvailability {
291    inner: Arc<Inner>,
292    /// The last state reported by this handle.
293    seen: u64,
294}
295
296#[derive(Debug, Clone)]
297struct WorkerAvailabilityTx {
298    inner: Arc<Inner>,
299}
300
301const UNAVAILABLE: u64 = 0;
302const AVAILABLE: u64 = 1;
303const FAILED: u64 = 2;
304const STATUS_BITS: u32 = 2;
305const STATUS_MASK: u64 = (1 << STATUS_BITS) - 1;
306
307#[derive(Debug)]
308struct Inner {
309    /// Change counter in the high bits, status in the low bits.
310    state: AtomicU64,
311    event: Event,
312}
313
314fn status(state: u64) -> WorkerStatus {
315    match state & STATUS_MASK {
316        AVAILABLE => WorkerStatus::Available,
317        FAILED => WorkerStatus::Failed,
318        _ => WorkerStatus::Unavailable,
319    }
320}
321
322impl Inner {
323    fn status(&self) -> WorkerStatus {
324        status(self.state.load(Ordering::Acquire))
325    }
326
327    /// Sets the status, returns `true` if it has changed.
328    ///
329    /// `Failed` is terminal.
330    fn update(&self, st: u64) -> bool {
331        let mut cur = self.state.load(Ordering::Acquire);
332        loop {
333            let old = cur & STATUS_MASK;
334            if old == st || old == FAILED {
335                return false;
336            }
337            let next = (((cur >> STATUS_BITS).wrapping_add(1)) << STATUS_BITS) | st;
338            match self
339                .state
340                .compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Acquire)
341            {
342                Ok(_) => return true,
343                Err(val) => cur = val,
344            }
345        }
346    }
347
348    fn set_failed(&self, notify: bool) {
349        self.update(FAILED);
350        if notify {
351            self.event.notify(usize::MAX);
352        }
353    }
354}
355
356impl WorkerAvailability {
357    fn create() -> (Self, WorkerAvailabilityTx) {
358        let inner = Arc::new(Inner {
359            state: AtomicU64::new(UNAVAILABLE),
360            event: Event::new(),
361        });
362
363        let avail = WorkerAvailability {
364            inner: inner.clone(),
365            seen: UNAVAILABLE,
366        };
367        let avail_tx = WorkerAvailabilityTx { inner };
368        (avail, avail_tx)
369    }
370
371    fn changed(&mut self) -> Option<WorkerStatus> {
372        let cur = self.inner.state.load(Ordering::Acquire);
373        let st = status(cur);
374        if cur != self.seen || st == WorkerStatus::Failed {
375            self.seen = cur;
376            Some(st)
377        } else {
378            None
379        }
380    }
381
382    async fn wait_for_update(&mut self) -> WorkerStatus {
383        loop {
384            if let Some(st) = self.changed() {
385                return st;
386            }
387            let listener = self.inner.event.listen();
388            if let Some(st) = self.changed() {
389                return st;
390            }
391            listener.await;
392        }
393    }
394}
395
396impl WorkerAvailabilityTx {
397    fn set(&self, val: bool) {
398        if self.inner.update(if val { AVAILABLE } else { UNAVAILABLE }) {
399            self.inner.event.notify(usize::MAX);
400        }
401    }
402}
403
404impl Drop for WorkerAvailabilityTx {
405    fn drop(&mut self) {
406        // the arbiter `on_stop` callback wakes the waiters. compio aborts the
407        // process if a task on another thread is woken during unwinding
408        self.inner.set_failed(!std::thread::panicking());
409    }
410}
411
412/// The worker's end of its item queue.
413///
414/// [`Worker`] handles keep the queue open, it is closed when the worker drops
415/// its end, so sends to a stopped worker fail.
416struct Queue<T>(Receiver<T>);
417
418impl<T> Drop for Queue<T> {
419    fn drop(&mut self) {
420        self.0.close();
421    }
422}
423
424/// Service runner.
425///
426/// The runner receives messages through an unbounded channel and processes them.
427struct ServiceRunner<F: ServerConfiguration<Item = Req>, Req> {
428    name: String,
429    factory: F,
430    svc: Pipeline<Req, (), ()>,
431    reqs: Queue<Req>,
432    stop: Pin<Box<dyn Stream<Item = Shutdown>>>,
433    availability: WorkerAvailabilityTx,
434    metrics: Option<Arc<WorkerSlot>>,
435}
436
437impl<F, Req> ServiceRunner<F, Req>
438where
439    Req: Send + 'static,
440    F: ServerConfiguration<Item = Req> + 'static,
441{
442    async fn create(
443        name: &str,
444        factory: F,
445        reqs: Queue<Req>,
446        stop: Receiver<Shutdown>,
447        availability: WorkerAvailabilityTx,
448        metrics: Option<Arc<WorkerSlot>>,
449    ) -> Result<Self, ()> {
450        availability.set(false);
451        let mut stop = Box::pin(stop);
452
453        let svc = match select(factory.create(), stream_recv(&mut stop)).await {
454            Either::Left(Ok(svc)) => Pipeline::new((), svc),
455            Either::Right(Some(Shutdown { result, .. })) => {
456                log::trace!("Shutdown uninitialized worker");
457                let _ = result.send(false);
458                return Err(());
459            }
460            Either::Left(Err(_)) | Either::Right(None) => return Err(()),
461        };
462        availability.set(true);
463
464        Ok(ServiceRunner {
465            factory,
466            svc,
467            reqs,
468            stop,
469            availability,
470            metrics,
471            name: name.to_string(),
472        })
473    }
474
475    async fn run(mut self) {
476        loop {
477            let mut recv = std::pin::pin!(self.reqs.0.recv());
478            let fut = poll_fn(|cx| {
479                match self.svc.poll_ready(cx) {
480                    Poll::Ready(Ok(())) => {
481                        self.availability.set(true);
482                        if let Some(ref m) = self.metrics {
483                            m.ready(true);
484                        }
485                    }
486                    Poll::Ready(Err(err)) => {
487                        self.availability.set(false);
488                        return Poll::Ready(Err(err));
489                    }
490                    Poll::Pending => {
491                        self.availability.set(false);
492                        if let Some(ref m) = self.metrics {
493                            m.ready(false);
494                        }
495                        return Poll::Pending;
496                    }
497                }
498
499                if let Ok(item) = ready!(recv.as_mut().poll(cx)) {
500                    Poll::Ready(Ok(Some(item)))
501                } else {
502                    log::error!("Server is gone");
503                    Poll::Ready(Ok(None))
504                }
505            });
506
507            match select(fut, stream_recv(&mut self.stop)).await {
508                Either::Left(Ok(Some(item))) => {
509                    // got item
510                    if let Some(ref m) = self.metrics {
511                        m.item();
512                    }
513                    let _ = self.svc.call(item).await;
514                    continue;
515                }
516                Either::Left(Err(())) => {
517                    // re-create service
518                    ntex_rt::spawn_detached(async move {
519                        self.svc.shutdown().await;
520                    });
521                }
522                Either::Right(Some(Shutdown { timeout, result })) => {
523                    log::info!("Shutting down {:?} worker gracefuly", self.name);
524                    self.availability.set(false);
525
526                    let timeout = if timeout.is_zero() { STOP_TIMEOUT } else { timeout };
527                    self.stop(timeout, Some(result)).await;
528                    return;
529                }
530                Either::Left(Ok(None)) | Either::Right(None) => {
531                    log::info!("Shutting down {:?} worker", self.name);
532                    self.availability.set(false);
533                    self.stop(STOP_TIMEOUT, None).await;
534                    return;
535                }
536            }
537
538            // re-create service
539            loop {
540                match select(self.factory.create(), stream_recv(&mut self.stop)).await {
541                    Either::Left(Ok(service)) => {
542                        self.svc = Pipeline::new((), service);
543                        break;
544                    }
545                    Either::Left(Err(_)) => sleep(Millis::ONE_SEC).await,
546                    Either::Right(_) => return,
547                }
548            }
549        }
550    }
551
552    async fn stop(&self, timeout: Millis, result: Option<oneshot::Sender<bool>>) {
553        // a stopped worker is not counted as not ready
554        if let Some(ref m) = self.metrics {
555            m.ready(true);
556        }
557        let res = timeout_checked(timeout, self.svc.shutdown()).await;
558        if let Some(result) = result {
559            let _ = result.send(res.is_ok());
560        }
561
562        log::info!("Worker {:?} has been stopped", self.name);
563    }
564}
565
566#[cfg(test)]
567mod tests {
568    #![allow(clippy::unused_async_trait_impl)]
569    use std::sync::atomic::AtomicUsize;
570    use std::task::{Wake, Waker};
571
572    use ntex_service::{Ctx, Service};
573
574    use super::*;
575
576    #[derive(Default)]
577    struct WakeCount(AtomicUsize);
578
579    impl Wake for WakeCount {
580        fn wake(self: Arc<Self>) {
581            self.0.fetch_add(1, Ordering::SeqCst);
582        }
583    }
584
585    fn poll<F: Future>(fut: Pin<&mut F>, wake: &Arc<WakeCount>) -> Poll<F::Output> {
586        let waker = Waker::from(wake.clone());
587        fut.poll(&mut Context::from_waker(&waker))
588    }
589
590    /// Every clone observes every status change.
591    #[test]
592    fn status_cloned_observers() {
593        let (mut a, tx) = WorkerAvailability::create();
594        let mut b = a.clone();
595        let (w1, w2) = (
596            Arc::new(WakeCount::default()),
597            Arc::new(WakeCount::default()),
598        );
599        {
600            let mut first = std::pin::pin!(a.wait_for_update());
601            let mut second = std::pin::pin!(b.wait_for_update());
602            assert!(poll(first.as_mut(), &w1).is_pending());
603            assert!(poll(second.as_mut(), &w2).is_pending());
604
605            tx.set(true);
606            assert_eq!(w1.0.load(Ordering::SeqCst), 1);
607            assert_eq!(w2.0.load(Ordering::SeqCst), 1);
608            assert_eq!(
609                poll(second.as_mut(), &w2),
610                Poll::Ready(WorkerStatus::Available)
611            );
612            assert_eq!(
613                poll(first.as_mut(), &w1),
614                Poll::Ready(WorkerStatus::Available)
615            );
616        }
617
618        // reported changes are not reported again
619        let mut c = a.clone();
620        assert!(poll(std::pin::pin!(a.wait_for_update()), &w1).is_pending());
621        assert!(poll(std::pin::pin!(c.wait_for_update()), &w1).is_pending());
622
623        // a change between calls is reported, with the current status
624        tx.set(false);
625        tx.set(true);
626        tx.set(false);
627        let st = poll(std::pin::pin!(a.wait_for_update()), &w1);
628        assert_eq!(st, Poll::Ready(WorkerStatus::Unavailable));
629        assert!(poll(std::pin::pin!(a.wait_for_update()), &w1).is_pending());
630
631        // failed is terminal and reported immediately to every clone
632        let mut d = a.clone();
633        let mut fut = std::pin::pin!(d.wait_for_update());
634        assert!(poll(fut.as_mut(), &w2).is_pending());
635        drop(tx);
636        assert_eq!(poll(fut.as_mut(), &w2), Poll::Ready(WorkerStatus::Failed));
637        for wrk in [&mut a, &mut b, &mut c] {
638            for _ in 0..2 {
639                let st = poll(std::pin::pin!(wrk.wait_for_update()), &w1);
640                assert_eq!(st, Poll::Ready(WorkerStatus::Failed));
641            }
642        }
643        assert_eq!(a.inner.status(), WorkerStatus::Failed);
644    }
645
646    /// The status cannot change after the worker has failed.
647    #[test]
648    fn status_failed_is_terminal() {
649        let (a, tx) = WorkerAvailability::create();
650        a.inner.set_failed(false);
651        tx.set(true);
652        assert_eq!(a.inner.status(), WorkerStatus::Failed);
653    }
654
655    #[derive(Clone)]
656    struct Cfg;
657
658    struct Srv;
659
660    impl Service<(), usize> for Srv {
661        type Res = ();
662        type Error = ();
663
664        async fn call(&self, _: usize, _: Ctx<'_, Self, ()>) -> Result<(), ()> {
665            Ok(())
666        }
667    }
668
669    impl ServerConfiguration for Cfg {
670        type Item = usize;
671        type Service = Srv;
672
673        async fn create(&self) -> std::io::Result<Srv> {
674            Ok(Srv)
675        }
676    }
677
678    /// The CPUs of a worker are known only if its placement is applied.
679    #[ntex::test]
680    async fn worker_cpus() {
681        let cpu = crate::affinity::plan(crate::Affinity::Cpu).pop();
682        for (placement, placed) in [
683            (Placement::cpu(usize::MAX >> 1), false),
684            (
685                cpu.unwrap_or_else(|| Placement::cpu(0)),
686                cfg!(any(windows, target_os = "linux", target_os = "android")),
687            ),
688        ] {
689            let mut wrk = Worker::start_with("test".into(), Cfg, Some(placement), None);
690            assert_eq!(wrk.wait_for_status().await, WorkerStatus::Available);
691            assert_eq!(!wrk.cpus().is_empty(), placed);
692            wrk.stop(Millis::ZERO).await;
693        }
694    }
695}