1use std::{
140 fmt::{self, Debug, Formatter},
141 pin::Pin,
142 sync::{Arc, RwLock},
143 task::{Context, Poll},
144};
145
146use futures_util::{
147 Future, FutureExt, StreamExt,
148 future::{BoxFuture, Shared},
149 stream::FuturesUnordered,
150};
151use tower_layer::Layer;
152use tower_service::Service;
153
154use crate::{
155 backend::{Backend, BackendConfig, future::BoxSyncFuture},
156 error::{BoxDynError, WorkerError},
157 monitor::{context::MonitorContext, shutdown::Shutdown},
158 task::Task,
159 worker::{
160 Worker,
161 context::WorkerContext,
162 event::{Event, EventHandlerBuilder},
163 lifecycle::LifecycleService,
164 },
165};
166
167pub mod context;
168pub mod shutdown;
169
170type WorkerFactory = Box<
171 dyn Fn(&mut WorkerContext) -> BoxFuture<'static, Result<(), WorkerError>>
172 + 'static
173 + Send
174 + Sync,
175>;
176
177type ShouldRestart =
178 Arc<RwLock<Option<Box<dyn Fn(&WorkerContext, &WorkerError) -> bool + 'static + Send + Sync>>>>;
179
180#[pin_project::pin_project]
181struct MonitoredWorker {
183 factory: WorkerFactory,
184 worker: WorkerContext,
185 #[pin]
186 fut: Option<BoxSyncFuture<Result<(), WorkerError>>>,
187 should_restart: ShouldRestart,
188}
189
190#[derive(Debug)]
192pub struct MonitoredWorkerError {
193 ctx: WorkerContext,
194 error: WorkerError,
195}
196
197impl Future for MonitoredWorker {
198 type Output = Result<(), MonitoredWorkerError>;
199
200 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
201 use std::panic::{AssertUnwindSafe, catch_unwind};
202
203 loop {
204 let mut this = self.as_mut().project();
205 let worker = this.worker;
206 let name = worker.name().to_owned();
207 debug!(?name, "polling worker");
208
209 if this.fut.is_none() {
211 let fut = (this.factory)(worker);
212 debug!(?name, "created new future");
213 this.fut.set(Some(fut.into()));
214 }
215
216 if worker.is_running() && worker.is_shutting_down() {
218 info!(?name, "worker shutting down, stopping");
219 if let Err(err) = worker.stop() {
220 error!(?name, ?err, "failed to stop worker");
221 return Poll::Ready(Err(MonitoredWorkerError {
222 ctx: worker.clone(),
223 error: err,
224 }));
225 }
226 continue;
227 }
228
229 let fut = this
230 .fut
231 .as_mut()
232 .get_mut()
233 .as_mut()
234 .expect("future was just set above; this should be unreachable");
235
236 let poll_result =
237 catch_unwind(AssertUnwindSafe(|| fut.poll_unpin(cx))).map_err(|err| {
238 let err = if let Some(s) = err.downcast_ref::<&str>() {
239 (*s).to_owned()
240 } else if let Some(s) = err.downcast_ref::<String>() {
241 s.clone()
242 } else {
243 "Unknown panic".to_owned()
244 };
245 WorkerError::PanicError(err)
246 });
247
248 match poll_result {
249 Ok(Poll::Pending) => return Poll::Pending,
250 Ok(Poll::Ready(Ok(()))) => return Poll::Ready(Ok(())),
251 Ok(Poll::Ready(Err(error))) | Err(error) => {
252 warn!(?name, ?error, "worker future ended with error");
253 let _fut = this.fut.as_mut().get_mut().take();
254
255 let should_restart = match this.should_restart.read() {
256 Ok(guard) => guard,
257 Err(poison_err) => {
258 error!(?name, ?poison_err, "should_restart lock poisoned");
259 return Poll::Ready(Err(MonitoredWorkerError {
260 ctx: worker.clone(),
261 error,
262 }));
263 }
264 };
265
266 match should_restart.as_ref().map(|s| s.as_ref()) {
267 Some(cb) => {
268 if !(cb)(worker, &error) {
269 info!(?name, "restart callback declined restart");
270 if let Err(stop_err) = worker.stop() {
271 error!(?name, ?stop_err, "failed to stop worker");
272 }
273 return Poll::Ready(Err(MonitoredWorkerError {
274 ctx: worker.clone(),
275 error,
276 }));
277 }
278 info!(?name, "restarting worker");
279 if let Err(restart_err) = worker.restart() {
280 error!(?name, ?restart_err, "failed to restart worker");
281 return Poll::Ready(Err(MonitoredWorkerError {
282 ctx: worker.clone(),
283 error,
284 }));
285 }
286 }
287 _ => {
288 return Poll::Ready(Err(MonitoredWorkerError {
289 ctx: worker.clone(),
290 error,
291 }));
292 }
293 }
294 }
295 }
296 }
297 }
298}
299
300#[derive(Default)]
302pub struct Monitor {
303 workers: Vec<MonitoredWorker>,
304 terminator: Option<Shared<BoxFuture<'static, ()>>>,
305 shutdown: Shutdown,
306 event_handler: EventHandlerBuilder,
307 should_restart: ShouldRestart,
308}
309
310impl Debug for Monitor {
311 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
312 f.debug_struct("Monitor")
313 .field("shutdown", &"[Graceful shutdown listener]")
314 .field("workers", &self.workers.len())
315 .finish()
316 }
317}
318
319impl Monitor {
320 fn run_worker<Args, S, FB, M>(
321 worker: Worker<Args, FB, S, M>,
322 ) -> BoxFuture<'static, Result<(), WorkerError>>
323 where
324 FB: BackendConfig + Backend<Task = Task<FB::Args>> + Send + Unpin + 'static,
325 S: Service<Task<FB::Args>> + Send + 'static,
326 FB::Args: Send + 'static,
327 Args: Send + 'static,
328 FB::Error: Into<BoxDynError> + Send + 'static,
329 M: Layer<LifecycleService<S>> + 'static,
330 FB::Layer: Layer<<M as Layer<LifecycleService<S>>>::Service>,
331 <FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service:
332 Service<Task<FB::Args>>,
333 <FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service: Send + 'static,
334 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
335 Task<<FB as BackendConfig>::Args>,
336 >>::Future: Send,
337 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
338 Task<<FB as BackendConfig>::Args>,
339 >>::Error: Into<BoxDynError> + Send + Sync + 'static,
340 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
341 Task<<FB as BackendConfig>::Args>,
342 >>::Response: Send + Sync + 'static,
343 {
344 let mut stream = worker.stream();
345 async move {
346 loop {
347 match stream.next().await {
348 Some(Err(e)) => return Err(e),
349 None => return Ok(()),
350 _ => (),
351 }
352 }
353 }
354 .boxed()
355 }
356 #[must_use]
377 pub fn register<Args, S, FB, M>(
378 mut self,
379 factory: impl Fn(usize) -> Worker<Args, FB, S, M> + 'static + Send + Sync,
380 ) -> Self
381 where
382 FB: BackendConfig + Backend<Task = Task<FB::Args>> + Send + Unpin + 'static,
383 S: Service<Task<FB::Args>> + Send + 'static,
384 FB::Args: Send + 'static,
385 Args: Send + 'static,
386 FB::Error: Into<BoxDynError> + Send + 'static,
387 M: Layer<LifecycleService<S>> + 'static,
388 FB::Layer: Layer<<M as Layer<LifecycleService<S>>>::Service>,
389 <FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service:
390 Service<Task<FB::Args>>,
391 <FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service: Send + 'static,
392 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
393 Task<<FB as BackendConfig>::Args>,
394 >>::Future: Send,
395 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
396 Task<<FB as BackendConfig>::Args>,
397 >>::Error: Into<BoxDynError> + Send + Sync + 'static,
398 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
399 Task<<FB as BackendConfig>::Args>,
400 >>::Response: Send + Sync + 'static,
401 {
402 let shutdown = Some(self.shutdown.clone());
403 let handler = self.event_handler.clone();
404
405 let (fut, worker) = {
406 let mut w = factory(0);
407 let ctx = &mut w.context;
408 ctx.bind_service::<M::Service>();
409 ctx.add_listener(move |ctx, ev| {
410 let handlers = handler.read();
411 if let Ok(handlers) = handlers {
412 for h in handlers.iter() {
413 h(ctx, ev);
414 }
415 }
416 });
417 ctx.shutdown = shutdown;
418 let c = ctx.clone();
419 let fut = Self::run_worker(w).into();
420 (Some(fut), c)
421 };
422
423 let worker = MonitoredWorker {
424 fut,
425 factory: Box::new(move |worker| {
426 let mut new_worker = factory(worker.restarts());
427
428 if worker.name().ne(new_worker.context.name()) {
430 let name = Arc::make_mut(&mut worker.name);
431 *name = new_worker.context.name().to_owned();
432 }
433
434 new_worker.context = worker.clone();
436
437 (Self::run_worker(new_worker)) as _
438 }),
439 worker,
440 should_restart: self.should_restart.clone(),
441 };
442 self.workers.push(worker);
443 self
444 }
445
446 pub fn register_n<Args, S, FB, M>(
467 mut self,
468 count: usize,
469 factory: impl Fn(usize, usize) -> Worker<Args, FB, S, M> + 'static + Send + Sync,
470 ) -> Self
471 where
472 FB: BackendConfig + Backend<Task = Task<FB::Args>> + Send + Unpin + 'static,
473 S: Service<Task<FB::Args>> + Send + 'static,
474 FB::Args: Send + 'static,
475 Args: Send + 'static,
476 FB::Error: Into<BoxDynError> + Send + 'static,
477 M: Layer<LifecycleService<S>> + 'static,
478 FB::Layer: Layer<<M as Layer<LifecycleService<S>>>::Service>,
479 <FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service:
480 Service<Task<FB::Args>>,
481 <FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service: Send + 'static,
482 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
483 Task<<FB as BackendConfig>::Args>,
484 >>::Future: Send,
485 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
486 Task<<FB as BackendConfig>::Args>,
487 >>::Error: Into<BoxDynError> + Send + Sync + 'static,
488 <<FB::Layer as Layer<<M as Layer<LifecycleService<S>>>::Service>>::Service as Service<
489 Task<<FB as BackendConfig>::Args>,
490 >>::Response: Send + Sync + 'static,
491 {
492 let factory = Arc::new(factory);
493 for index in 0..count {
494 let f = factory.clone();
495 self = self.register(move |id| f(index, id));
496 }
497 self
498 }
499
500 pub async fn run_with_signal<S>(self, signal: S) -> Result<(), MonitorError>
516 where
517 S: Send + Future<Output = std::io::Result<()>>,
518 {
519 let shutdown = self.shutdown.clone();
520 let ctx = self.context();
521 let shutdown_after = async move {
522 let res = signal.await;
523 let _ = ctx.shutdown();
524 res
525 };
526 if let Some(terminator) = self.terminator {
527 let _res = futures_util::future::select(
528 Self::run_all_workers(self.workers, shutdown).boxed(),
529 async {
530 let res = shutdown_after.await;
531 terminator.await;
532 res.map_err(MonitorError::ShutdownSignal)
533 }
534 .boxed(),
535 )
536 .await;
537 } else {
538 let runner = self.run();
539 let res = futures_util::join!(shutdown_after, runner); match res {
541 (Ok(_), Ok(_)) => {
542 }
544 (Err(e), Ok(_) | Err(_)) => return Err(e.into()),
545 (Ok(_), Err(e)) => return Err(e),
546 }
547 }
548 Ok(())
549 }
550
551 pub async fn run(self) -> Result<(), MonitorError> {
561 let shutdown = self.shutdown.clone();
562 let shutdown_future = self.shutdown.boxed().map(|_| ());
563 let (result, _) = futures_util::join!(
564 Self::run_all_workers(self.workers, shutdown),
565 shutdown_future,
566 );
567
568 result
569 }
570 async fn run_all_workers(
571 workers: Vec<MonitoredWorker>,
572 shutdown: Shutdown,
573 ) -> Result<(), MonitorError> {
574 let results: Vec<_> = FuturesUnordered::from_iter(workers).collect().await;
577
578 shutdown.start_shutdown();
579
580 let mut errors = Vec::new();
581 for r in results {
583 match r {
584 Ok(_) => {}
585 Err(MonitoredWorkerError { ctx, error }) => {
586 errors.push(MonitoredWorkerError { ctx, error })
587 }
588 }
589 }
590 if !errors.is_empty() {
591 return Err(MonitorError::ExitError(ExitError(errors)));
592 }
593 Ok(())
594 }
595
596 #[must_use]
598 pub fn on_event<F: Fn(&WorkerContext, &Event) + Send + Sync + 'static>(self, f: F) -> Self {
599 let _ = self.event_handler.write().map(|mut res| {
600 let _ = res.insert(Box::new(f));
601 });
602 self
603 }
604}
605
606impl Monitor {
607 #[must_use]
613 pub fn new() -> Self {
614 Self::default()
615 }
616
617 #[cfg(feature = "sleep")]
627 #[must_use]
628 pub fn shutdown_timeout(self, duration: std::time::Duration) -> Self {
629 self.with_terminator(crate::timer::sleep(duration))
630 }
631
632 #[must_use]
640 pub fn with_terminator(mut self, fut: impl Future<Output = ()> + Send + 'static) -> Self {
641 self.terminator = Some(fut.boxed().shared());
642 self
643 }
644
645 #[must_use]
647 pub fn should_restart<F>(self, cb: F) -> Self
648 where
649 F: Fn(&WorkerContext, &WorkerError) -> bool + Send + Sync + 'static,
650 {
651 let _ = self.should_restart.write().map(|mut res| {
652 let _ = res.insert(Box::new(cb));
653 });
654 self
655 }
656
657 #[must_use]
663 pub fn context(&self) -> MonitorContext {
664 MonitorContext {
665 shutdown: self.shutdown.clone(),
666 workers: self.workers.iter().map(|w| w.worker.clone()).collect(),
667 }
668 }
669}
670
671#[derive(Debug, thiserror::Error)]
673#[non_exhaustive]
674pub enum MonitorError {
675 #[error("Worker errors:\n{0}")]
677 ExitError(#[from] ExitError),
678
679 #[error("Shutdown signal error: {0}")]
681 ShutdownSignal(#[from] std::io::Error),
682}
683
684impl fmt::Debug for ExitError {
685 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
686 std::fmt::Display::fmt(&self, f)
687 }
688}
689
690#[derive(thiserror::Error)]
692pub struct ExitError(pub Vec<MonitoredWorkerError>);
693
694impl std::fmt::Display for ExitError {
695 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
696 writeln!(f, "MonitoredErrors:")?;
697 for worker in &self.0 {
698 writeln!(f, " - Worker `{}`: {}", worker.ctx.name(), worker.error)?;
699 }
700 Ok(())
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707 use crate::{
708 backend::{TaskSink, dequeue::VecDequeBackend},
709 task::task_id::TaskId,
710 worker::context::WorkerContext,
711 };
712 use core::panic;
713 use std::{io, time::Duration};
714
715 use tokio::time::sleep;
716 use tower::limit::ConcurrencyLimitLayer;
717
718 use crate::{monitor::Monitor, worker::builder::WorkerBuilder};
719
720 #[tokio::test]
721 async fn basic_with_workers() {
722 let mut backend = VecDequeBackend::new();
723
724 for i in 0..10 {
725 backend.push(i).await.unwrap();
726 }
727
728 let monitor: Monitor = Monitor::new();
729 let monitor = monitor.register(move |index| {
730 WorkerBuilder::new(format!("rango-tango-{index}"))
731 .backend(backend.clone())
732 .build(move |r: u32, id: TaskId, w: WorkerContext| async move {
733 println!("{id:?}, {}", w.name());
734 tokio::time::sleep(Duration::from_secs(index as u64)).await;
735 Ok::<_, io::Error>(r)
736 })
737 });
738 let m = monitor.context();
739 tokio::spawn(async move {
740 sleep(Duration::from_millis(1500)).await;
741 m.shutdown().unwrap();
742 });
743 monitor.run().await.unwrap();
744 }
745 #[tokio::test]
746 async fn test_monitor_run() {
747 let mut backend = VecDequeBackend::new();
748
749 for i in 0..10 {
750 backend.push(i).await.unwrap();
751 }
752
753 let monitor: Monitor = Monitor::new()
754 .register(move |index| {
755 WorkerBuilder::new(format!("rango-tango-{index}"))
756 .backend(backend.clone())
757 .layer(ConcurrencyLimitLayer::new(1))
758 .build(move |r| async move {
759 sleep(Duration::from_millis(1000)).await;
760 if r % 2 == 0 {
761 panic!("Brrr")
762 }
763 })
764 })
765 .should_restart(|worker, e| {
766 let restarts = worker.restarts();
767 println!(
768 "Encountered error in {} with {e:?} for attempt {restarts}",
769 worker.name()
770 );
771 if restarts < 3 {
772 return true;
773 }
774 false
775 })
776 .on_event(|wrk, e| {
777 println!("{}: {e:?}", wrk.name());
778 });
779 assert_eq!(monitor.workers.len(), 1);
780 let m = monitor.context();
781
782 tokio::spawn(async move {
783 sleep(Duration::from_millis(10000)).await;
784 m.shutdown().unwrap();
785 });
786
787 let result = monitor.run().await;
788 dbg!(&result);
789 assert!(
790 result.is_err_and(|e| matches!(e, MonitorError::ExitError(_))),
791 "Monitor did not return an error as expected"
792 );
793 }
794
795 #[tokio::test]
796 async fn test_monitor_register_multiple() {
797 let mut int_backend = VecDequeBackend::new();
798
799 let mut str_backend = VecDequeBackend::new();
800
801 for i in 0..10 {
802 int_backend.push(i).await.unwrap();
803 }
804
805 for i in 0..10 {
806 str_backend.push(i.to_string()).await.unwrap();
807 }
808
809 let monitor: Monitor = Monitor::new();
810
811 assert_send_sync::<Monitor>();
812
813 let monitor = monitor.on_event(|wrk, e| {
814 println!("{:?}, {e:?}", wrk.name());
815 });
816 let monitor = monitor
817 .register(move |index| {
818 WorkerBuilder::new(format!("worker0-{index}"))
819 .backend(int_backend.clone())
820 .layer(ConcurrencyLimitLayer::new(1))
821 .build(
822 move |request: i32, id: TaskId, w: WorkerContext| async move {
823 println!("{id:?}, {}", w.name());
824 tokio::time::sleep(Duration::from_secs(1)).await;
825 Ok::<_, io::Error>(request)
826 },
827 )
828 })
829 .register(move |index| {
830 WorkerBuilder::new(format!("worker1-{index}"))
831 .backend(str_backend.clone())
832 .layer(ConcurrencyLimitLayer::new(1))
833 .build(
834 move |request: String, id: TaskId, w: WorkerContext| async move {
835 println!("{id:?}, {}", w.name());
836 tokio::time::sleep(Duration::from_secs(1)).await;
837 Ok::<_, io::Error>(request)
838 },
839 )
840 });
841 assert_eq!(monitor.workers.len(), 2);
842 let m = monitor.context();
843
844 tokio::spawn(async move {
845 sleep(Duration::from_millis(5000)).await;
846 m.shutdown().unwrap();
847 });
848
849 let result = monitor.run().await;
850 assert!(result.is_ok());
851 }
852
853 #[tokio::test]
862 async fn shutdown_wakes_idle_workers() {
863 use crate::backend::memory::MemoryStorage;
864
865 const WORKERS: usize = 31;
866
867 let mut monitor = Monitor::new();
868 for index in 0..WORKERS {
869 monitor = monitor.register(move |_| {
870 WorkerBuilder::new(format!("idle-{index}"))
871 .backend(MemoryStorage::<u32>::new())
874 .build(|_: u32| async {})
875 });
876 }
877
878 let ctx = monitor.context();
879 tokio::spawn(async move {
880 sleep(Duration::from_millis(100)).await;
881 ctx.shutdown().unwrap();
882 });
883
884 let result = tokio::time::timeout(Duration::from_secs(5), monitor.run()).await;
885
886 let exit = result.expect("monitor did not shut down idle workers");
887 exit.unwrap();
888 }
889
890 #[tokio::test]
892 async fn signal_shutdown_drains_active_work_with_many_idle_workers() {
893 use crate::backend::memory::MemoryStorage;
894 use std::sync::atomic::{AtomicBool, Ordering};
895
896 const IDLE_WORKERS: usize = 40;
897
898 let mut busy_backend = VecDequeBackend::new();
899 busy_backend.push(1u32).await.unwrap();
900
901 let completed = Arc::new(AtomicBool::new(false));
902 let flag = completed.clone();
903
904 let mut monitor = Monitor::new().register(move |_| {
905 let flag = flag.clone();
906 WorkerBuilder::new("busy")
907 .backend(busy_backend.clone())
908 .build(move |_: u32| {
909 let flag = flag.clone();
910 async move {
911 sleep(Duration::from_millis(300)).await;
912 flag.store(true, Ordering::SeqCst);
913 }
914 })
915 });
916
917 for index in 0..IDLE_WORKERS {
918 monitor = monitor.register(move |_| {
919 WorkerBuilder::new(format!("idle-{index}"))
920 .backend(MemoryStorage::<u32>::new())
921 .build(|_: u32| async {})
922 });
923 }
924
925 let signal = async {
926 sleep(Duration::from_millis(50)).await;
927 Ok(())
928 };
929
930 let result =
931 tokio::time::timeout(Duration::from_secs(5), monitor.run_with_signal(signal)).await;
932
933 let exit = result.expect("monitor did not shut down idle workers");
934 exit.unwrap();
935 assert!(
936 completed.load(Ordering::SeqCst),
937 "shutdown cut short the in-flight task"
938 );
939 }
940
941 fn assert_send_sync<T: Send + Sync>() {}
942}