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, PipelineBinding, Service, ServiceFactory};
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
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    tx1: Sender<T>,
41    tx2: Sender<Shutdown>,
42    avail: WorkerAvailability,
43}
44
45impl<T> cmp::Ord for Worker<T> {
46    fn cmp(&self, other: &Self) -> cmp::Ordering {
47        self.name.cmp(&other.name)
48    }
49}
50
51impl<T> cmp::PartialOrd for Worker<T> {
52    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
53        Some(self.cmp(other))
54    }
55}
56
57impl<T> hash::Hash for Worker<T> {
58    fn hash<H: hash::Hasher>(&self, state: &mut H) {
59        self.name.hash(state);
60    }
61}
62
63impl<T> Eq for Worker<T> {}
64
65impl<T> PartialEq for Worker<T> {
66    fn eq(&self, other: &Worker<T>) -> bool {
67        self.name == other.name
68    }
69}
70
71#[derive(Debug)]
72/// Stop worker process
73///
74/// Stop future resolves when worker completes processing
75/// incoming items and stop arbiter
76pub struct WorkerStop(oneshot::AsyncReceiver<bool>);
77
78impl<T> Worker<T> {
79    /// Start worker.
80    pub fn start<F>(name: String, cfg: F, cid: Option<CoreId>) -> Worker<T>
81    where
82        T: Send + 'static,
83        F: ServerConfiguration<Item = T>,
84    {
85        let (tx1, rx1) = unbounded();
86        let (tx2, rx2) = unbounded();
87        let (avail, avail_tx) = WorkerAvailability::create();
88        let name2 = name.clone();
89        let inner = avail.inner.clone();
90
91        let worker = Worker {
92            tx1,
93            tx2,
94            avail,
95            name: name.clone(),
96        };
97
98        Arbiter::with_name(name)
99            .on_stop(move || {
100                inner.failed.store(true, Ordering::Release);
101                inner.updated.store(true, Ordering::Release);
102                inner.available.store(false, Ordering::Release);
103                inner.waker.wake();
104            })
105            .handle()
106            .spawn(async move {
107                if let Some(cid) = cid
108                    && core_affinity::set_for_current(cid)
109                {
110                    log::info!("Set affinity to {cid:?} for worker {name2:?}");
111                }
112
113                spawn(async move {
114                    log::info!("Starting worker {name2:?}");
115
116                    log::debug!("Creating server instance in {name2:?}");
117                    let factory = cfg.create().await;
118
119                    match create(name2.clone(), rx1, rx2, factory, avail_tx).await {
120                        Ok((svc, wrk)) => {
121                            log::debug!("Server instance has been created in {name2:?}");
122                            run_worker(svc, wrk).await;
123                        }
124                        Err(e) => {
125                            log::error!("Cannot start worker {name2:?}: {e:?}");
126                        }
127                    }
128                    Arbiter::current().stop();
129                });
130            });
131
132        worker
133    }
134
135    /// Worker name
136    pub fn name(&self) -> &str {
137        &self.name
138    }
139
140    /// Send message to the worker.
141    ///
142    /// Returns `Ok` if message got accepted by the worker.
143    /// Otherwise return message back as `Err`
144    pub fn send(&self, msg: T) -> Result<(), T> {
145        self.tx1.try_send(msg).map_err(TrySendError::into_inner)
146    }
147
148    /// Check worker status.
149    pub fn status(&self) -> WorkerStatus {
150        if self.avail.failed() {
151            WorkerStatus::Failed
152        } else if self.avail.available() {
153            WorkerStatus::Available
154        } else {
155            WorkerStatus::Unavailable
156        }
157    }
158
159    /// Wait for worker status updates
160    pub async fn wait_for_status(&mut self) -> WorkerStatus {
161        if self.avail.failed() {
162            WorkerStatus::Failed
163        } else {
164            self.avail.wait_for_update().await;
165            self.status()
166        }
167    }
168
169    /// Stop worker.
170    ///
171    /// If timeout value is zero, force shutdown worker
172    pub fn stop(&self, timeout: Millis) -> WorkerStop {
173        let (result, rx) = oneshot::async_channel();
174        let _ = self.tx2.try_send(Shutdown { timeout, result });
175        WorkerStop(rx)
176    }
177}
178
179impl<T> Clone for Worker<T> {
180    fn clone(&self) -> Self {
181        Worker {
182            tx1: self.tx1.clone(),
183            tx2: self.tx2.clone(),
184            name: self.name.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 worker
276///
277/// Worker accepts message via unbounded channel and starts processing.
278struct WorkerSt<T, F: ServiceFactory<T>> {
279    name: String,
280    rx: Receiver<T>,
281    stop: Pin<Box<dyn Stream<Item = Shutdown>>>,
282    factory: F,
283    availability: WorkerAvailabilityTx,
284}
285
286async fn run_worker<T, F>(mut svc: PipelineBinding<F::Service, T>, mut wrk: WorkerSt<T, F>)
287where
288    T: Send + 'static,
289    F: ServiceFactory<T> + 'static,
290{
291    loop {
292        let mut recv = std::pin::pin!(wrk.rx.recv());
293        let fut = poll_fn(|cx| {
294            match svc.poll_ready(cx) {
295                Poll::Ready(Ok(())) => {
296                    wrk.availability.set(true);
297                }
298                Poll::Ready(Err(err)) => {
299                    wrk.availability.set(false);
300                    return Poll::Ready(Err(err));
301                }
302                Poll::Pending => {
303                    wrk.availability.set(false);
304                    return Poll::Pending;
305                }
306            }
307
308            if let Ok(item) = ready!(recv.as_mut().poll(cx)) {
309                let fut = svc.call(item);
310                spawn(async move {
311                    let _ = fut.await;
312                });
313                Poll::Ready(Ok::<_, F::Error>(true))
314            } else {
315                log::error!("Server is gone");
316                Poll::Ready(Ok(false))
317            }
318        });
319
320        match select(fut, stream_recv(&mut wrk.stop)).await {
321            Either::Left(Ok(true)) => continue,
322            Either::Left(Err(_)) => {
323                ntex_rt::spawn(async move {
324                    svc.shutdown().await;
325                });
326            }
327            Either::Right(Some(Shutdown { timeout, result })) => {
328                wrk.availability.set(false);
329
330                let timeout = if timeout.is_zero() { STOP_TIMEOUT } else { timeout };
331
332                stop_svc(&wrk.name, svc, timeout, Some(result)).await;
333                return;
334            }
335            Either::Left(Ok(false)) | Either::Right(None) => {
336                wrk.availability.set(false);
337                stop_svc(&wrk.name, svc, STOP_TIMEOUT, None).await;
338                return;
339            }
340        }
341
342        // re-create service
343        loop {
344            match select(wrk.factory.create(()), stream_recv(&mut wrk.stop)).await {
345                Either::Left(Ok(service)) => {
346                    svc = Pipeline::new(service).bind();
347                    break;
348                }
349                Either::Left(Err(_)) => sleep(Millis::ONE_SEC).await,
350                Either::Right(_) => return,
351            }
352        }
353    }
354}
355
356async fn stop_svc<T, F>(
357    name: &str,
358    svc: PipelineBinding<F, T>,
359    timeout: Millis,
360    result: Option<oneshot::Sender<bool>>,
361) where
362    T: Send + 'static,
363    F: Service<T> + 'static,
364{
365    let res = timeout_checked(timeout, svc.shutdown()).await;
366    if let Some(result) = result {
367        let _ = result.send(res.is_ok());
368    }
369
370    log::info!("Worker {name:?} has been stopped");
371}
372
373async fn create<T, F>(
374    name: String,
375    rx: Receiver<T>,
376    stop: Receiver<Shutdown>,
377    factory: Result<F, ()>,
378    availability: WorkerAvailabilityTx,
379) -> Result<(PipelineBinding<F::Service, T>, WorkerSt<T, F>), ()>
380where
381    T: Send + 'static,
382    F: ServiceFactory<T> + 'static,
383{
384    availability.set(false);
385    let factory = factory?;
386    let mut stop = Box::pin(stop);
387
388    let svc = match select(factory.create(()), stream_recv(&mut stop)).await {
389        Either::Left(Ok(svc)) => Pipeline::new(svc).bind(),
390        Either::Right(Some(Shutdown { result, .. })) => {
391            log::trace!("Shutdown uninitialized worker");
392            let _ = result.send(false);
393            return Err(());
394        }
395        Either::Left(Err(_)) | Either::Right(None) => return Err(()),
396    };
397    availability.set(true);
398
399    Ok((
400        svc,
401        WorkerSt {
402            name,
403            rx,
404            factory,
405            availability,
406            stop: Box::pin(stop),
407        },
408    ))
409}