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