dynamic_add/
dynamic_add.rs

1//! Dynamic `ProcessManager` demo.
2//!
3//! Showcases
4//! ---------
5//! 1. starting a manager with one child,
6//! 2. adding new `Runnable`s *after* the manager is already running,
7//! 3. shutting the whole tree down gracefully.
8//!
9//! Build & run
10//! -----------
11//! ```bash
12//! cargo run --example dynamic_add
13//! ```
14mod simple;
15
16use processmanager::*;
17use std::{sync::Arc, time::Duration};
18use tokio::time::{interval, sleep};
19
20/// A lightweight long-running component that emits a heartbeat.
21struct Worker {
22    id: usize,
23    guard: Arc<RuntimeGuard>,
24}
25
26impl Worker {
27    fn new(id: usize) -> Self {
28        Self {
29            id,
30            guard: Arc::new(RuntimeGuard::default()),
31        }
32    }
33}
34
35impl Runnable for Worker {
36    fn process_start(&self) -> ProcFuture<'_> {
37        let id = self.id;
38        let guard = self.guard.clone();
39
40        Box::pin(async move {
41            let ticker = guard.runtime_ticker().await;
42            let mut beat = interval(Duration::from_secs(1));
43
44            loop {
45                match ticker.tick(beat.tick()).await {
46                    ProcessOperation::Next(_) => println!("worker-{id}: heartbeat"),
47                    ProcessOperation::Control(RuntimeControlMessage::Reload) => {
48                        println!("worker-{id}: received *reload*")
49                    }
50                    ProcessOperation::Control(RuntimeControlMessage::Shutdown) => {
51                        println!("worker-{id}: shutting down");
52                        break;
53                    }
54                    // absorb any future control messages we don't explicitly handle
55                    ProcessOperation::Control(_) => continue,
56                }
57            }
58            Ok(())
59        })
60    }
61
62    fn process_handle(&self) -> Arc<dyn ProcessControlHandler> {
63        self.guard.handle()
64    }
65}
66
67#[tokio::main]
68async fn main() {
69    // ------------------------------------------------------------------
70    // 1. Manager with a single initial worker
71    // ------------------------------------------------------------------
72    let mgr = ProcessManagerBuilder::default()
73        .pre_insert(Worker::new(0))
74        .build();
75
76    // We need to keep access to the manager after starting it, therefore
77    // wrap it in an `Arc` and clone it for the spawning task.
78    let mgr: Arc<ProcessManager> = Arc::new(mgr);
79    let mgr_clone = Arc::clone(&mgr);
80
81    tokio::spawn(async move {
82        mgr_clone
83            .process_start()
84            .await
85            .expect("manager encountered an error");
86    });
87
88    let handle = mgr.process_handle();
89
90    // ------------------------------------------------------------------
91    // 2. Dynamically add workers
92    // ------------------------------------------------------------------
93    println!("==> main: sleeping 3 s before adding worker-1");
94    sleep(Duration::from_secs(3)).await;
95
96    println!("==> main: adding worker-1");
97    mgr.add(Worker::new(1));
98
99    println!("==> main: sleeping 2 s");
100    sleep(Duration::from_secs(2)).await;
101
102    println!("==> main: adding worker-2");
103    mgr.add(Worker::new(2));
104
105    // ------------------------------------------------------------------
106    // 3. Graceful shutdown after a short delay
107    // ------------------------------------------------------------------
108    println!("==> main: running 5 s before global shutdown");
109    sleep(Duration::from_secs(5)).await;
110
111    println!("==> main: initiating graceful shutdown");
112    handle.shutdown().await;
113
114    // Allow children to print their exit messages
115    sleep(Duration::from_secs(1)).await;
116}