harn_vm/connectors/
active_clients.rs1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use async_trait::async_trait;
6
7use super::{ClientError, ConnectorClient, ProviderId};
8
9#[async_trait]
12pub trait ConnectorClientResolver: Send + Sync {
13 async fn resolve(
14 &self,
15 provider: &str,
16 ) -> Result<Option<Arc<dyn ConnectorClient>>, ClientError>;
17}
18
19#[derive(Clone, Default)]
23pub struct VmConnectorClients {
24 clients: Arc<BTreeMap<String, Arc<dyn ConnectorClient>>>,
25 resolver: Option<Arc<dyn ConnectorClientResolver>>,
26}
27
28impl VmConnectorClients {
29 pub fn new(
30 clients: BTreeMap<ProviderId, Arc<dyn ConnectorClient>>,
31 resolver: Option<Arc<dyn ConnectorClientResolver>>,
32 ) -> Self {
33 Self {
34 clients: Arc::new(client_map(clients)),
35 resolver,
36 }
37 }
38
39 pub async fn resolve(
40 &self,
41 provider: &str,
42 ) -> Result<Option<Arc<dyn ConnectorClient>>, ClientError> {
43 if let Some(resolver) = self.resolver.as_ref() {
47 if let Some(client) = resolver.resolve(provider).await? {
48 return Ok(Some(client));
49 }
50 }
51 Ok(self.clients.get(provider).cloned())
52 }
53}
54
55thread_local! {
56 static ACTIVE_CONNECTOR_CLIENTS: RefCell<BTreeMap<String, Arc<dyn ConnectorClient>>> =
57 RefCell::new(BTreeMap::new());
58}
59
60pub fn install_active_connector_clients(clients: BTreeMap<ProviderId, Arc<dyn ConnectorClient>>) {
61 ACTIVE_CONNECTOR_CLIENTS.with(|slot| *slot.borrow_mut() = client_map(clients));
62}
63
64pub fn scope_active_connector_clients(
68 clients: BTreeMap<ProviderId, Arc<dyn ConnectorClient>>,
69) -> ActiveConnectorClientsGuard {
70 let previous = ACTIVE_CONNECTOR_CLIENTS
71 .with(|slot| std::mem::replace(&mut *slot.borrow_mut(), client_map(clients)));
72 ActiveConnectorClientsGuard { previous }
73}
74
75pub struct ActiveConnectorClientsGuard {
76 previous: BTreeMap<String, Arc<dyn ConnectorClient>>,
77}
78
79impl Drop for ActiveConnectorClientsGuard {
80 fn drop(&mut self) {
81 ACTIVE_CONNECTOR_CLIENTS.with(|slot| {
82 *slot.borrow_mut() = std::mem::take(&mut self.previous);
83 });
84 }
85}
86
87pub fn active_connector_client(provider: &str) -> Option<Arc<dyn ConnectorClient>> {
88 ACTIVE_CONNECTOR_CLIENTS.with(|slot| slot.borrow().get(provider).cloned())
89}
90
91pub fn clear_active_connector_clients() {
92 ACTIVE_CONNECTOR_CLIENTS.with(|slot| slot.borrow_mut().clear());
93}
94
95fn client_map(
96 clients: BTreeMap<ProviderId, Arc<dyn ConnectorClient>>,
97) -> BTreeMap<String, Arc<dyn ConnectorClient>> {
98 clients
99 .into_iter()
100 .map(|(provider, client)| (provider.as_str().to_string(), client))
101 .collect()
102}
103
104#[cfg(test)]
105mod tests {
106 use std::sync::atomic::{AtomicUsize, Ordering};
107
108 use async_trait::async_trait;
109 use serde_json::Value as JsonValue;
110
111 use super::*;
112 use crate::connectors::ClientError;
113
114 struct NamedClient(&'static str);
115
116 #[async_trait]
117 impl ConnectorClient for NamedClient {
118 async fn call(&self, _method: &str, _args: JsonValue) -> Result<JsonValue, ClientError> {
119 Ok(JsonValue::String(self.0.to_string()))
120 }
121 }
122
123 struct OnceResolver {
124 initializations: AtomicUsize,
125 clients: tokio::sync::OnceCell<BTreeMap<ProviderId, Arc<dyn ConnectorClient>>>,
126 }
127
128 #[async_trait]
129 impl ConnectorClientResolver for OnceResolver {
130 async fn resolve(
131 &self,
132 provider: &str,
133 ) -> Result<Option<Arc<dyn ConnectorClient>>, ClientError> {
134 let clients = self
135 .clients
136 .get_or_init(|| async {
137 self.initializations.fetch_add(1, Ordering::SeqCst);
138 tokio::task::yield_now().await;
139 BTreeMap::from([(
140 ProviderId::from("core"),
141 Arc::new(NamedClient("project")) as Arc<dyn ConnectorClient>,
142 )])
143 })
144 .await;
145 Ok(clients.get(&ProviderId::from(provider)).cloned())
146 }
147 }
148
149 #[test]
150 fn nested_client_scope_restores_the_host_projection() {
151 clear_active_connector_clients();
152 install_active_connector_clients(BTreeMap::from([(
153 ProviderId::from("outer"),
154 Arc::new(NamedClient("outer")) as Arc<dyn ConnectorClient>,
155 )]));
156
157 {
158 let _inner = scope_active_connector_clients(BTreeMap::from([(
159 ProviderId::from("inner"),
160 Arc::new(NamedClient("inner")) as Arc<dyn ConnectorClient>,
161 )]));
162 assert!(active_connector_client("inner").is_some());
163 assert!(active_connector_client("outer").is_none());
164 }
165
166 assert!(active_connector_client("outer").is_some());
167 assert!(active_connector_client("inner").is_none());
168 clear_active_connector_clients();
169 }
170
171 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
172 async fn vm_resolver_is_once_only_across_tasks_and_overrides_core_clients() {
173 let resolver = Arc::new(OnceResolver {
174 initializations: AtomicUsize::new(0),
175 clients: tokio::sync::OnceCell::new(),
176 });
177 let clients = VmConnectorClients::new(
178 BTreeMap::from([(
179 ProviderId::from("core"),
180 Arc::new(NamedClient("builtin")) as Arc<dyn ConnectorClient>,
181 )]),
182 Some(resolver.clone()),
183 );
184
185 let tasks = (0..8)
186 .map(|_| {
187 let clients = clients.clone();
188 tokio::spawn(async move {
189 let client = clients
190 .resolve("core")
191 .await
192 .expect("resolve")
193 .expect("project override");
194 client.call("ping", JsonValue::Null).await.expect("call")
195 })
196 })
197 .collect::<Vec<_>>();
198 for task in tasks {
199 assert_eq!(
200 task.await.expect("task"),
201 JsonValue::String("project".to_string())
202 );
203 }
204 assert_eq!(resolver.initializations.load(Ordering::SeqCst), 1);
205 }
206}