Skip to main content

autoschematic_core/
connector_cache.rs

1use std::{
2    collections::HashMap,
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use crate::{
8    config::{self, AutoschematicConfig},
9    connector::{
10        Connector, ConnectorInbox, FilterResponse,
11        handle::{ConnectorHandle, ConnectorHandleStatus},
12        spawn::spawn_connector,
13    },
14    connector_util::check_connector_host_version_match,
15    error::AutoschematicError,
16    keystore::KeyStore,
17    util::parse_env_file,
18};
19
20use anyhow::Context;
21use dashmap::DashMap;
22use serde::Serialize;
23use tokio::task::JoinSet;
24
25#[derive(Debug, Clone, Serialize)]
26pub enum InitStatus {
27    Offline,
28    Spawning,
29    Initializing,
30    Error(String),
31    Running,
32}
33
34#[derive(Debug, Serialize)]
35pub struct TopResponse {
36    handle_status: ConnectorHandleStatus,
37    init_status: InitStatus,
38}
39
40#[derive(Clone, PartialEq, Eq, Hash, Serialize)]
41pub struct ConnectorCacheKey {
42    pub prefix: PathBuf,
43    pub shortname: String,
44}
45
46pub type ConnectorCacheValue = (Arc<dyn ConnectorHandle>, ConnectorInbox);
47
48#[derive(Default)]
49pub struct ConnectorCache {
50    cache: Arc<DashMap<ConnectorCacheKey, ConnectorCacheValue>>,
51    init_status: Arc<DashMap<ConnectorCacheKey, InitStatus>>,
52    /// Used to cache the results of Connector::filter(addr), which are assumed to be
53    /// static. Since filter() is the most common call, this can speed up workflows by
54    /// avoiding calling out to the connectors so many times.
55    filter_cache: Arc<DashMap<ConnectorCacheKey, HashMap<PathBuf, FilterResponse>>>,
56    // TODO add doc_cache
57    // binary_cache: BinaryCache,
58}
59
60/// A ConnectorCache represents a handle to multiple Connector instances. The server, CLI, and LSP
61/// implementations all use a ConnectorCache to initialize connectors on-demand.
62impl ConnectorCache {
63    pub async fn top(&self) -> HashMap<ConnectorCacheKey, TopResponse> {
64        let mut res = HashMap::new();
65
66        // Note: This looks redundant, but avoids a lifetime issue with DashMap: Map not being general enough...
67        let keys: Vec<ConnectorCacheKey> = self.cache.iter().map(|kv| kv.key().clone()).collect();
68
69        for key in keys {
70            if let Some(connector) = self.cache.get(&key).map(|kv| kv.0.clone()) {
71                let init_status = match self.init_status.get(&key) {
72                    Some(init_status) => init_status.value().clone(),
73                    None => InitStatus::Initializing,
74                };
75
76                res.insert(
77                    key,
78                    TopResponse {
79                        handle_status: connector.status().await,
80                        init_status,
81                    },
82                );
83            }
84        }
85
86        res
87    }
88
89    pub async fn get_connector(&self, name: &str, prefix: &Path) -> Option<(Arc<dyn Connector>, ConnectorInbox)> {
90        let key = ConnectorCacheKey {
91            shortname: name.into(),
92            prefix: prefix.into(),
93        };
94
95        if let Some(entry) = self.cache.get(&key) {
96            let (connector, inbox) = &*entry;
97            Some((connector.clone(), inbox.resubscribe()))
98        } else {
99            None
100        }
101    }
102
103    pub async fn get_or_spawn_connector(
104        &self,
105        config: &AutoschematicConfig,
106        prefix: &str,
107        connector_def: &config::Connector,
108        keystore: Option<Arc<dyn KeyStore>>,
109        do_init: bool,
110    ) -> Result<(Arc<dyn Connector>, ConnectorInbox), AutoschematicError> {
111        let key = ConnectorCacheKey {
112            shortname: connector_def.shortname.clone(),
113            prefix: prefix.into(),
114        };
115
116        let Some(prefix_def) = config.prefixes.get(prefix) else {
117            return Err(anyhow::anyhow!(format!("No such prefix {}", prefix)).into());
118        };
119
120        let spec = &connector_def.spec;
121
122        let mut env = HashMap::new();
123
124        if let Some(ref env_file) = prefix_def.env_file {
125            for (k, v) in parse_env_file(&std::fs::read_to_string(env_file).context(format!("Reading env file {}", env_file))?)
126            {
127                env.insert(k, v);
128            }
129        }
130
131        for (k, v) in &prefix_def.env {
132            env.insert(k.into(), v.into());
133        }
134
135        if let Some(ref env_file) = connector_def.env_file {
136            for (k, v) in parse_env_file(&std::fs::read_to_string(env_file).context(format!("Reading env file {}", env_file))?)
137            {
138                env.insert(k, v);
139            }
140        }
141
142        for (k, v) in &connector_def.env {
143            env.insert(k.into(), v.into());
144        }
145
146        if let Some((connector, inbox)) = self.cache.get(&key).map(|entry| {
147            let (connector, inbox) = &*entry;
148            (connector.clone(), inbox.resubscribe())
149        }) {
150            let need_init = match self.init_status.entry(key.clone()) {
151                dashmap::Entry::Occupied(status_ref) => match status_ref.get() {
152                    InitStatus::Offline => do_init,
153                    InitStatus::Spawning => do_init,
154                    InitStatus::Initializing => false,
155                    InitStatus::Error(_) => do_init,
156                    InitStatus::Running => false,
157                },
158                dashmap::Entry::Vacant(vacant_entry) => {
159                    vacant_entry.insert(InitStatus::Offline);
160                    do_init
161                }
162            };
163
164            if need_init {
165                self.init_status.insert(key.clone(), InitStatus::Initializing);
166                if let Err(e) = connector.init().await {
167                    tracing::error!(
168                        "In prefix {}: failed to init connector {}: {:#?}",
169                        prefix,
170                        connector_def.shortname,
171                        e
172                    );
173
174                    self.init_status.insert(key.clone(), InitStatus::Error(format!("{:#?}", e)));
175                } else {
176                    self.init_status.insert(key.clone(), InitStatus::Running);
177                }
178            }
179
180            Ok((connector.clone(), inbox.resubscribe()))
181        } else {
182            self.init_status.insert(key.clone(), InitStatus::Spawning);
183            // In order for the first process that invokes connector_init to receive the earliest messages from the inbox,
184            //  we need to pass the original inbox, and not the resubscribed copy.
185            // Hence the song and dance below with the Arc and resubscribe().
186            let (connector, inbox) = spawn_connector(&connector_def.shortname, spec, &PathBuf::from(prefix), &env, keystore)
187                .await
188                .context("spawn_connector()")?;
189
190            check_connector_host_version_match(&connector_def.shortname, &connector).await?;
191
192            if do_init {
193                self.init_status.insert(key.clone(), InitStatus::Initializing);
194                if let Err(e) = connector.init().await {
195                    tracing::error!(
196                        "In prefix {}: failed to init connector {}: {:#?}",
197                        prefix,
198                        connector_def.shortname,
199                        e
200                    );
201                    self.init_status.insert(key.clone(), InitStatus::Error(format!("{:#?}", e)));
202                } else {
203                    self.init_status.insert(key.clone(), InitStatus::Running);
204                }
205            }
206
207            let connector_arc = Arc::new(connector);
208
209            self.cache.insert(key, (connector_arc.clone(), inbox.resubscribe()));
210
211            Ok((connector_arc, inbox))
212        }
213        // }
214    }
215
216    pub async fn init_connector(&self, name: &str, prefix: &Path) -> Option<anyhow::Result<()>> {
217        let key = ConnectorCacheKey {
218            shortname: name.into(),
219            prefix: prefix.into(),
220        };
221
222        if let Some(entry) = self.cache.get(&key) {
223            let (connector, _inbox) = &*entry;
224            self.clear_filter_cache(name, prefix).await;
225            Some(connector.init().await)
226        } else {
227            None
228        }
229    }
230
231    /// Since Connector::filter() must be a static function by contract,
232    /// we cache its results to avoid expensive RPC calls.
233    /// Note that this does not initialize connectors if they aren't yet present.
234    /// Also, note that calling init() on a connector will invalidate the cached filter data.
235    pub async fn filter_cached(&self, name: &str, prefix: &Path, addr: &Path) -> anyhow::Result<FilterResponse> {
236        let key = ConnectorCacheKey {
237            shortname: name.into(),
238            prefix: prefix.into(),
239        };
240
241        // Get the cached value of Connector::filter(addr) for connector `name` at prefix `prefix`, or fetch it and cache it.
242        if let Some(value) = self.filter_cache.get(&key).and_then(|cache| cache.get(addr).copied()) {
243            Ok(value)
244        } else if let Some(connector) = self.cache.get(&key).map(|entry| entry.0.clone()) {
245            let res = connector.filter(addr).await?;
246            self.filter_cache.entry(key.clone()).or_default().insert(addr.into(), res);
247            Ok(res)
248        } else {
249            Ok(FilterResponse::none())
250        }
251    }
252
253    pub async fn filter_all_cached(
254        &self,
255        autoschematic_config: &AutoschematicConfig,
256        addr: &Path,
257    ) -> anyhow::Result<FilterResponse> {
258        for (prefix_name, prefix_def) in &autoschematic_config.prefixes {
259            for connector_def in &prefix_def.connectors {
260                match self
261                    .filter_cached(&connector_def.shortname, &PathBuf::from(prefix_name), addr)
262                    .await?
263                {
264                    FilterResponse::None => continue,
265                    resp => return Ok(resp),
266                }
267            }
268        }
269        Ok(FilterResponse::None)
270    }
271
272    pub async fn clear_filter_cache(&self, name: &str, prefix: &Path) {
273        let key = ConnectorCacheKey {
274            shortname: name.into(),
275            prefix: prefix.into(),
276        };
277
278        self.filter_cache.remove(&key);
279    }
280
281    /// Drop all entries in the connector and filter caches.
282    /// This should in theory kill all connectors that encapsulate running processes
283    /// by calling their Drop impl.
284    pub async fn clear(&self) {
285        let keys: Vec<ConnectorCacheKey> = self.cache.iter().map(|kv| kv.key().clone()).collect();
286
287        let mut joinset = JoinSet::new();
288
289        for key in keys {
290            if let Some(kv) = self.cache.get(&key) {
291                let connector = kv.0.clone();
292                joinset.spawn(async move { connector.kill().await });
293            }
294        }
295
296        joinset.join_all().await;
297
298        self.cache.clear();
299        self.filter_cache.clear();
300    }
301}
302
303// TODO we'll revisit this later...
304// #[async_trait]
305// impl Connector for Arc<ConnectorCache> {
306//     async fn new(name: &str, prefix: &Path, outbox: ConnectorOutbox) -> Result<Arc<dyn Connector>, anyhow::Error>
307//     where
308//         Self: Sized,
309//     {
310//         bail!("ConnectorCache::new() is a stub!")
311//         // let connector_cache = ConnectorCache::default();
312//         // Ok(Arc::new(connector_cache))
313//     }
314
315//     async fn init(&self) -> anyhow::Result<()> {
316//         let mut joinset: JoinSet<anyhow::Result<()>> = JoinSet::new();
317
318//         for (prefix, prefix_def) in &self.config.prefixes {
319//             for connector_def in &prefix_def.connectors {
320//                 let cache = self.clone();
321//                 let prefix = prefix.clone();
322//                 let connector_def = connector_def.clone();
323
324//                 joinset.spawn(async move {
325//                     cache
326//                         .get_or_spawn_connector(
327//                             &connector_def.shortname,
328//                             &connector_def.spec,
329//                             &PathBuf::from(prefix),
330//                             &connector_def.env,
331//                             cache.keystore.clone(),
332//                             true,
333//                         )
334//                         .await?;
335//                     Ok(())
336//                 });
337//             }
338//         }
339
340//         while let Some(res) = joinset.join_next().await {}
341
342//         Ok(())
343//     }
344
345//     async fn filter(&self, addr: &Path) -> Result<FilterResponse, anyhow::Error> {
346//         let keys: Vec<ConnectorCacheKey> = self.cache.iter().map(|kv| kv.key().clone()).collect();
347
348//         let mut joinset: JoinSet<anyhow::Result<FilterResponse>> = JoinSet::new();
349
350//         for key in keys {
351//             let cache = self.clone();
352//             let cache_addr = addr.to_owned();
353//             joinset.spawn(async move { cache.filter_cached(&key.shortname, &key.prefix, &cache_addr).await });
354//         }
355
356//         while let Some(res) = joinset.join_next().await {
357//             match res?? {
358//                 FilterResponse::None => continue,
359//                 other => return Ok(other),
360//             }
361//         }
362
363//         Ok(FilterResponse::None)
364//     }
365// }