kanit

Toy init system
Log | Files | Refs | README | LICENSE

control.rs (5431B)


      1 #[cfg(not(feature = "testing"))]
      2 use std::io::{Write, stdin, stdout};
      3 
      4 use async_process::driver;
      5 use log::{debug, error, info, warn};
      6 
      7 #[cfg(not(feature = "testing"))]
      8 use kanit_common::error::Context;
      9 use kanit_common::error::{Error, Result};
     10 use kanit_diagnostics::tap as kanit_tap;
     11 use kanit_diagnostics::timing as kanit_timing;
     12 use kanit_executor::{join_all, spawn};
     13 use kanit_unit::{RcUnit, UnitName};
     14 
     15 use crate::loader;
     16 use crate::loader::Loader;
     17 
     18 #[cfg(not(feature = "testing"))]
     19 fn critical_unit_fail(err: Error) -> Result<()> {
     20     loop {
     21         error!("a critical unit failed to start, continue? [y/n]: ");
     22 
     23         let _ = stdout().flush();
     24 
     25         let mut input = String::new();
     26 
     27         match stdin().read_line(&mut input) {
     28             Ok(_) => match input.chars().next() {
     29                 Some('y') => return Ok(()),
     30                 Some('n') => return Err(err),
     31                 _ => {}
     32             },
     33             Err(e) => return Err(e).context("failed to read stdin"),
     34         }
     35     }
     36 }
     37 
     38 #[cfg(feature = "testing")]
     39 fn critical_unit_fail(err: Error) -> Result<()> {
     40     kanit_tap::bail(Some(err.to_string()));
     41 
     42     Err(err)
     43 }
     44 
     45 async fn start_unit(tuple: (usize, RcUnit)) -> Result<Option<UnitName>> {
     46     let (j, unit) = tuple;
     47 
     48     let mut unit_b = unit.borrow_mut();
     49 
     50     debug!("loading unit {}", unit_b.name());
     51 
     52     let id = kanit_timing::push_scope(format!("unit:{}", unit_b.name()));
     53 
     54     if !unit_b.prepare().await? {
     55         warn!("failed preparations for {}", unit_b.name());
     56         kanit_tap::not_ok(j + 1, Some("failed preparations"));
     57         return Ok(None);
     58     }
     59 
     60     if let Err(e) = unit_b.start().await {
     61         if e.is_recoverable() {
     62             warn!("{e}");
     63             kanit_tap::not_ok(j + 1, Some(e));
     64         } else {
     65             error!("{e}");
     66             critical_unit_fail(e)?;
     67         }
     68 
     69         kanit_timing::pop_scope(id);
     70 
     71         return Ok(None);
     72     }
     73 
     74     kanit_timing::pop_scope(id);
     75 
     76     kanit_tap::ok(j + 1, Some(unit_b.name()));
     77 
     78     debug!("finished loading unit {}", unit_b.name());
     79 
     80     Ok(Some(unit_b.name().clone()))
     81 }
     82 
     83 async fn start_level(i: usize, name: Box<str>) -> Result<()> {
     84     let mut loader = Loader::obtain()?.borrow_mut();
     85     let level = loader.grouping.get(&name).cloned().unwrap_or_else(Vec::new);
     86 
     87     info!("starting level {name}");
     88 
     89     let scope_str = format!("level:{name}");
     90 
     91     kanit_tap::enter_subtest(Some(&scope_str));
     92 
     93     let id = kanit_timing::push_scope(&scope_str);
     94 
     95     kanit_tap::plan(level.len());
     96 
     97     for (j, group) in level.into_iter().enumerate() {
     98         let group_str = format!("group:{j}");
     99 
    100         kanit_tap::enter_subtest(Some(&group_str));
    101         kanit_tap::plan(group.len());
    102 
    103         let handles = join_all(group.into_iter().enumerate().map(start_unit)).await;
    104 
    105         for handle in handles {
    106             if let Some(n) = handle? {
    107                 loader.mark_started(n);
    108             }
    109         }
    110 
    111         kanit_tap::exit_subtest();
    112         kanit_tap::ok(j + 1, Some(&group_str));
    113     }
    114 
    115     kanit_timing::pop_scope(id);
    116 
    117     kanit_tap::exit_subtest();
    118     kanit_tap::ok(i + 1, Some(&scope_str));
    119 
    120     Ok(())
    121 }
    122 
    123 pub async fn start() -> Result<()> {
    124     kanit_timing::register();
    125 
    126     loader::init_loader()?;
    127 
    128     // include teardown as well
    129     // sysboot, boot, default * 2
    130     kanit_tap::plan(6);
    131 
    132     let driver_task = spawn(driver());
    133 
    134     start_level(0, Box::from("sysboot")).await?;
    135     start_level(1, Box::from("boot")).await?;
    136     start_level(2, Box::from("default")).await?;
    137 
    138     driver_task.cancel().await;
    139 
    140     {
    141         let loader = Loader::obtain()?;
    142 
    143         let _ = loader.borrow().save();
    144     }
    145 
    146     Ok(())
    147 }
    148 
    149 async fn stop_unit(tuple: (usize, RcUnit)) -> Result<()> {
    150     let (j, unit) = tuple;
    151 
    152     let mut unit_b = unit.borrow_mut();
    153 
    154     debug!("unloading unit {}", unit_b.name());
    155 
    156     match unit_b.stop().await {
    157         Ok(_) => kanit_tap::ok(j + 1, Some(unit_b.name())),
    158         Err(e) => {
    159             kanit_tap::not_ok(j + 1, Some(unit_b.name()));
    160 
    161             if e.is_recoverable() {
    162                 warn!("{e}")
    163             } else {
    164                 error!("{e}") // won't stop still
    165             }
    166         }
    167     }
    168 
    169     debug!("finished unloading unit {}", unit_b.name());
    170 
    171     Ok(())
    172 }
    173 
    174 async fn teardown_level(total: usize, i: usize, name: Box<str>) -> Result<()> {
    175     let loader = Loader::obtain()?.borrow();
    176     let level = loader.grouping.get(&name).cloned().unwrap_or_else(Vec::new);
    177 
    178     info!("stopping level {name}");
    179 
    180     let scope_str = format!("level:{name}-stop");
    181     kanit_tap::enter_subtest(Some(&scope_str));
    182 
    183     kanit_tap::plan(level.len());
    184 
    185     for (j, group) in level.into_iter().enumerate() {
    186         let group_str = format!("group:{j}");
    187 
    188         kanit_tap::enter_subtest(Some(&group_str));
    189         kanit_tap::plan(group.len());
    190 
    191         let handles = join_all(group.into_iter().enumerate().map(stop_unit)).await;
    192 
    193         for handle in handles {
    194             handle?;
    195         }
    196 
    197         kanit_tap::exit_subtest();
    198         kanit_tap::ok(j + 1, Some(&group_str));
    199     }
    200 
    201     kanit_tap::exit_subtest();
    202     kanit_tap::ok(total + (total - i), Some(&scope_str));
    203 
    204     Ok(())
    205 }
    206 
    207 pub async fn teardown() -> Result<()> {
    208     let driver_task = spawn(driver());
    209 
    210     teardown_level(3, 2, Box::from("default")).await?;
    211     teardown_level(3, 1, Box::from("boot")).await?;
    212     teardown_level(3, 0, Box::from("sysboot")).await?;
    213 
    214     driver_task.cancel().await;
    215 
    216     Ok(())
    217 }