Skip to main content

mitosis/
ctx.rs

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
14/// Events a slow follower may fall behind by before it skips some.
15const 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    /// Read through `config()`; `set_config` swaps it on a reload.
26    config: RwLock<Config>,
27    pub store: Mutex<Store>,
28    pub backend: Arc<dyn CloneBackend>,
29    /// Postgres installs found on this machine; looked up again when a request finds no match.
30    installs: RwLock<Vec<PgInstall>>,
31    pub started_at: Instant,
32    /// Template names with a build in progress (one build per name at a time).
33    pub(crate) building: Mutex<HashSet<String>>,
34    /// Clone ids with a create or destroy still running; gc and the reaper leave them alone.
35    pub(crate) in_flight: Mutex<HashMap<String, InFlightOp>>,
36    /// Clone ids with a parent-exit watcher thread, so gc does not start a second one.
37    pub(crate) watching: Mutex<HashSet<String>>,
38    /// Clone creation reads a template folder under the read guard; a template swap renames and
39    /// removes folders under the write guard, so no clone copies a half-moved template. One lock
40    /// for all templates: the guarded work is a copy-on-write clone or a swap.
41    pub(crate) template_swap: tokio::sync::RwLock<()>,
42    /// Process groups of running seed commands and dump/restore pipes; killed on daemon shutdown.
43    pub(crate) build_groups: Arc<Mutex<HashSet<i32>>>,
44    /// Woken whenever a clone goes away or the settings change, so queued creates look again.
45    pub room: tokio::sync::Notify,
46    /// Held by the one create at the head of the queue from its limit check until its row is
47    /// reserved; tokio's mutex hands it out in arrival order, which is the FIFO order.
48    pub(crate) admission: Arc<tokio::sync::Mutex<()>>,
49    /// Creates waiting for room (including the one at the head).
50    pub(crate) queued: AtomicUsize,
51    /// Every event `events::emit` writes, for `mitosis events --follow` connections.
52    pub events_tx: tokio::sync::broadcast::Sender<EventRow>,
53    /// Router connections, cancel keys and listener status; read through `router()`.
54    router: Mutex<RouterState>,
55    /// Clone id → the lock one wake or sleep of that clone holds; others wait on it.
56    pub(crate) waking: Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
57    /// Test hook: background layered builds skipped because the checkout was on another branch
58    /// (only the first such skip leaves an event).
59    #[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    /// `open` with a given back end instead of the one the settings pick (tests wrap it).
71    #[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    /// A snapshot of the current settings; a later reload does not change it.
118    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    /// A snapshot of the known Postgres installs, newest first.
130    pub fn installs(&self) -> Vec<PgInstall> {
131        self.installs
132            .read()
133            .unwrap_or_else(|e| e.into_inner())
134            .clone()
135    }
136
137    /// Searches the machine for Postgres installs again (e.g. one was installed since start).
138    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    /// The install of `major`; on a miss the machine is searched once more before giving up.
146    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    /// The newest install; on a miss the machine is searched once more before giving up.
155    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    /// Locks the router state; never hold it across an await or a store call.
164    pub fn router(&self) -> MutexGuard<'_, RouterState> {
165        self.router.lock().unwrap_or_else(|e| e.into_inner())
166    }
167
168    /// Locks the store; use it for one store call at a time, never across Postgres work.
169    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}