xet_runtime/core/
common.rs1use std::any::Any;
2use std::collections::HashMap;
3use std::sync::atomic::AtomicU64;
4use std::sync::{Arc, Mutex};
5
6use reqwest::Client;
7use tokio::sync::Semaphore;
8
9use crate::config::XetConfig;
10use crate::utils::adjustable_semaphore::AdjustableSemaphore;
11
12pub struct XetCommon {
16 pub file_ingestion_semaphore: Arc<Semaphore>,
18
19 pub file_download_semaphore: Arc<Semaphore>,
21
22 pub reconstruction_download_buffer: Arc<AdjustableSemaphore>,
24
25 pub active_downloads: Arc<AtomicU64>,
27
28 runtime_cache: Mutex<HashMap<String, Box<dyn Any + Send + Sync>>>,
32}
33
34impl XetCommon {
35 pub fn new(config: &XetConfig) -> Self {
37 Self {
38 file_ingestion_semaphore: Arc::new(Semaphore::new(config.data.max_concurrent_file_ingestion)),
39 file_download_semaphore: Arc::new(Semaphore::new(config.data.max_concurrent_file_downloads)),
40 reconstruction_download_buffer: {
41 let base = config.reconstruction.download_buffer_size.as_u64();
42 let limit = config.reconstruction.download_buffer_limit.as_u64();
43 let limit = if limit < base {
47 tracing::warn!(
48 "download_buffer_limit ({limit}) is below download_buffer_size ({base}); using {base}"
49 );
50 base
51 } else {
52 limit
53 };
54 AdjustableSemaphore::new(base, (base, limit))
55 },
56 active_downloads: Arc::new(AtomicU64::new(0)),
57 runtime_cache: Mutex::new(HashMap::new()),
58 }
59 }
60
61 pub fn cache_get_or_create<T, F>(&self, key: &str, create: F) -> T
67 where
68 T: Clone + Send + Sync + 'static,
69 F: FnOnce() -> T,
70 {
71 let mut guard = self.runtime_cache.lock().unwrap();
72 if let Some(existing) = guard.get(key)
73 && let Some(val) = existing.downcast_ref::<T>()
74 {
75 return val.clone();
76 }
77 let value = create();
78 guard.insert(key.to_string(), Box::new(value.clone()));
79 value
80 }
81
82 pub fn get_or_create_reqwest_client<F>(&self, tag: String, create_client_fn: F) -> crate::error::Result<Client>
91 where
92 F: FnOnce() -> std::result::Result<Client, reqwest::Error>,
93 {
94 let client_cache: Arc<Mutex<Option<(String, Client)>>> =
95 self.cache_get_or_create("global_reqwest_client", || Arc::new(Mutex::new(None)));
96 let mut guard = client_cache.lock()?;
97
98 match guard.as_ref() {
99 Some((cached_tag, cached_client)) if cached_tag == &tag => Ok(cached_client.clone()),
100 _ => {
101 let new_client = create_client_fn()?;
102 *guard = Some((tag, new_client.clone()));
103 Ok(new_client)
104 },
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use std::sync::atomic::{AtomicUsize, Ordering};
112
113 use super::*;
114
115 #[test]
116 fn test_get_or_create_reqwest_client_caches_by_tag() {
117 let common = XetCommon::new(&XetConfig::new());
118 let call_count = AtomicUsize::new(0);
119
120 let _client1 = common
121 .get_or_create_reqwest_client("test-tag".to_string(), || {
122 call_count.fetch_add(1, Ordering::SeqCst);
123 reqwest::Client::builder().build()
124 })
125 .unwrap();
126
127 let _client2 = common
128 .get_or_create_reqwest_client("test-tag".to_string(), || {
129 call_count.fetch_add(1, Ordering::SeqCst);
130 reqwest::Client::builder().build()
131 })
132 .unwrap();
133
134 assert_eq!(call_count.load(Ordering::SeqCst), 1);
135 }
136
137 #[test]
138 fn test_get_or_create_reqwest_client_creates_new_for_different_tag() {
139 let common = XetCommon::new(&XetConfig::new());
140 let call_count = AtomicUsize::new(0);
141
142 let _client1 = common
143 .get_or_create_reqwest_client("tag1".to_string(), || {
144 call_count.fetch_add(1, Ordering::SeqCst);
145 reqwest::Client::builder().user_agent("client1").build()
146 })
147 .unwrap();
148
149 let _client2 = common
150 .get_or_create_reqwest_client("tag2".to_string(), || {
151 call_count.fetch_add(1, Ordering::SeqCst);
152 reqwest::Client::builder().user_agent("client2").build()
153 })
154 .unwrap();
155
156 assert_eq!(call_count.load(Ordering::SeqCst), 2);
157 }
158
159 #[test]
160 fn test_replaces_client_when_tag_changes() {
161 let common = XetCommon::new(&XetConfig::new());
162
163 let _client1 = common
164 .get_or_create_reqwest_client("tcp".to_string(), || {
165 reqwest::Client::builder().user_agent("tcp-client").build()
166 })
167 .unwrap();
168
169 let _client2 = common
170 .get_or_create_reqwest_client("/tmp/socket.sock".to_string(), || {
171 reqwest::Client::builder().user_agent("uds-client").build()
172 })
173 .unwrap();
174
175 let call_count = AtomicUsize::new(0);
178 let _client3 = common
179 .get_or_create_reqwest_client("/tmp/socket.sock".to_string(), || {
180 call_count.fetch_add(1, Ordering::SeqCst);
181 reqwest::Client::builder().build()
182 })
183 .unwrap();
184 assert_eq!(call_count.load(Ordering::SeqCst), 0);
185 }
186
187 #[test]
188 fn test_semaphores_initialized_from_config() {
189 let config = XetConfig::new();
190 let common = XetCommon::new(&config);
191
192 assert_eq!(common.file_ingestion_semaphore.available_permits(), config.data.max_concurrent_file_ingestion);
193 assert_eq!(common.file_download_semaphore.available_permits(), config.data.max_concurrent_file_downloads);
194
195 assert!(
198 common.reconstruction_download_buffer.total_permits()
199 >= config.reconstruction.download_buffer_size.as_u64()
200 );
201
202 assert_eq!(common.active_downloads.load(Ordering::Relaxed), 0);
203 }
204
205 #[test]
206 fn test_cache_get_or_create_returns_cached_value() {
207 let common = XetCommon::new(&XetConfig::new());
208 let call_count = AtomicUsize::new(0);
209
210 let v1: Arc<Mutex<Vec<i32>>> = common.cache_get_or_create("my_cache", || {
211 call_count.fetch_add(1, Ordering::SeqCst);
212 Arc::new(Mutex::new(vec![1, 2, 3]))
213 });
214
215 let v2: Arc<Mutex<Vec<i32>>> = common.cache_get_or_create("my_cache", || {
216 call_count.fetch_add(1, Ordering::SeqCst);
217 Arc::new(Mutex::new(vec![4, 5, 6]))
218 });
219
220 assert_eq!(call_count.load(Ordering::SeqCst), 1);
221 assert!(Arc::ptr_eq(&v1, &v2));
222 }
223
224 #[test]
225 fn test_cache_different_keys_are_independent() {
226 let common = XetCommon::new(&XetConfig::new());
227
228 let v1: Arc<String> = common.cache_get_or_create("key_a", || Arc::new("alpha".to_string()));
229 let v2: Arc<String> = common.cache_get_or_create("key_b", || Arc::new("beta".to_string()));
230
231 assert_eq!(v1.as_str(), "alpha");
232 assert_eq!(v2.as_str(), "beta");
233 }
234
235 #[test]
236 fn test_cache_type_mismatch_creates_new_entry() {
237 let common = XetCommon::new(&XetConfig::new());
238
239 let _v1: Arc<String> = common.cache_get_or_create("shared_key", || Arc::new("original".to_string()));
240
241 let v2: Arc<i32> = common.cache_get_or_create("shared_key", || Arc::new(42));
244 assert_eq!(*v2, 42);
245 }
246}