1use crate::clone_backend::{same_volume, select_backend, CloneBackend};
2pub use crate::config::Config;
3use crate::error::{ErrorCode, MitosisError, Result};
4use crate::paths::Home;
5use crate::pg::{discover_with, find_major, newest, PgInstall};
6use crate::protocol::EventRow;
7use crate::router::RouterState;
8use crate::state::Store;
9use std::collections::{HashMap, HashSet};
10use std::sync::atomic::AtomicUsize;
11use std::sync::{Arc, Mutex, MutexGuard, RwLock};
12use std::time::Instant;
13
14const EVENT_CHANNEL: usize = 1024;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub(crate) enum InFlightOp {
19 Create,
20 Destroy,
21}
22
23pub struct Ctx {
24 pub home: Home,
25 config: RwLock<Config>,
27 pub store: Mutex<Store>,
28 pub backend: Arc<dyn CloneBackend>,
29 installs: RwLock<Vec<PgInstall>>,
31 pub started_at: Instant,
32 pub(crate) building: Mutex<HashSet<String>>,
34 pub(crate) in_flight: Mutex<HashMap<String, InFlightOp>>,
36 pub(crate) watching: Mutex<HashSet<String>>,
38 pub(crate) template_swap: tokio::sync::RwLock<()>,
42 pub(crate) build_groups: Arc<Mutex<HashSet<i32>>>,
44 pub room: tokio::sync::Notify,
46 pub(crate) admission: Arc<tokio::sync::Mutex<()>>,
49 pub(crate) queued: AtomicUsize,
51 pub events_tx: tokio::sync::broadcast::Sender<EventRow>,
53 router: Mutex<RouterState>,
55 pub(crate) waking: Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
57 #[doc(hidden)]
60 pub layered_skips: AtomicUsize,
61}
62
63impl Ctx {
64 pub fn open(home: Home, config: Config) -> Result<Ctx> {
65 home.ensure_dirs()?;
66 let backend: Arc<dyn CloneBackend> = select_backend(&home, &config)?;
67 Ctx::open_with_backend(home, config, backend)
68 }
69
70 #[doc(hidden)]
72 pub fn open_with_backend(
73 home: Home,
74 config: Config,
75 backend: Arc<dyn CloneBackend>,
76 ) -> Result<Ctx> {
77 home.ensure_dirs()?;
78 backend.check_supported(&home.root)?;
79 if !same_volume(&home.templates_dir(), &home.clones_dir())? {
80 return Err(MitosisError::new(
81 ErrorCode::CrossVolume,
82 format!(
83 "{} and {} are on different volumes",
84 home.templates_dir().display(),
85 home.clones_dir().display()
86 ),
87 )
88 .with_hint(
89 "keep templates/ and clones/ on the same volume (no symlinks to other disks)",
90 ));
91 }
92 let store = Store::open(&home.state_db())?;
93 let installs = discover_with(Some(&home));
94 let router = Mutex::new(RouterState::new(config.router_port));
95 Ok(Ctx {
96 home,
97 config: RwLock::new(config),
98 store: Mutex::new(store),
99 backend,
100 installs: RwLock::new(installs),
101 started_at: Instant::now(),
102 building: Mutex::new(HashSet::new()),
103 in_flight: Mutex::new(HashMap::new()),
104 watching: Mutex::new(HashSet::new()),
105 template_swap: tokio::sync::RwLock::new(()),
106 build_groups: Arc::new(Mutex::new(HashSet::new())),
107 room: tokio::sync::Notify::new(),
108 admission: Arc::new(tokio::sync::Mutex::new(())),
109 queued: AtomicUsize::new(0),
110 events_tx: tokio::sync::broadcast::channel(EVENT_CHANNEL).0,
111 router,
112 waking: Mutex::new(HashMap::new()),
113 layered_skips: AtomicUsize::new(0),
114 })
115 }
116
117 pub fn config(&self) -> Config {
119 self.config
120 .read()
121 .unwrap_or_else(|e| e.into_inner())
122 .clone()
123 }
124
125 pub fn set_config(&self, config: Config) {
126 *self.config.write().unwrap_or_else(|e| e.into_inner()) = config;
127 }
128
129 pub fn installs(&self) -> Vec<PgInstall> {
131 self.installs
132 .read()
133 .unwrap_or_else(|e| e.into_inner())
134 .clone()
135 }
136
137 async fn rediscover(&self) {
139 let home = self.home.clone();
140 if let Ok(found) = tokio::task::spawn_blocking(move || discover_with(Some(&home))).await {
141 *self.installs.write().unwrap_or_else(|e| e.into_inner()) = found;
142 }
143 }
144
145 pub async fn install_major(&self, major: u32) -> Result<PgInstall> {
147 if let Ok(i) = find_major(&self.installs(), major) {
148 return Ok(i.clone());
149 }
150 self.rediscover().await;
151 find_major(&self.installs(), major).cloned()
152 }
153
154 pub async fn newest_install(&self) -> Result<PgInstall> {
156 if let Ok(i) = newest(&self.installs()) {
157 return Ok(i.clone());
158 }
159 self.rediscover().await;
160 newest(&self.installs()).cloned()
161 }
162
163 pub fn router(&self) -> MutexGuard<'_, RouterState> {
165 self.router.lock().unwrap_or_else(|e| e.into_inner())
166 }
167
168 pub fn store(&self) -> MutexGuard<'_, Store> {
170 self.store.lock().unwrap_or_else(|e| e.into_inner())
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn open_creates_layout() {
180 let tmp = tempfile::tempdir().unwrap();
181 let ctx = Ctx::open(Home::at(tmp.path().join("m")), Config::default()).unwrap();
182 assert!(ctx.home.templates_dir().is_dir());
183 assert!(ctx.home.state_db().is_file());
184 assert!(ctx.store().list_templates().unwrap().is_empty());
185 }
186
187 #[allow(dead_code)]
188 fn ctx_is_send_sync(c: Ctx) -> impl Send + Sync {
189 c
190 }
191}