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