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)]
19struct Shutdown {
21 timeout: Millis,
22 result: oneshot::Sender<bool>,
23}
24
25#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
26pub enum WorkerStatus {
28 Available,
29 #[default]
30 Unavailable,
31 Failed,
32}
33
34#[derive(Debug)]
35pub 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)]
72pub struct WorkerStop(oneshot::AsyncReceiver<bool>);
77
78impl<T> Worker<T> {
79 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 pub fn name(&self) -> &str {
137 &self.name
138 }
139
140 pub fn send(&self, msg: T) -> Result<(), T> {
145 self.tx1.try_send(msg).map_err(TrySendError::into_inner)
146 }
147
148 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 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 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
275struct 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 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}