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 AdjustableSemaphore::new(base, (base, limit))
44 },
45 active_downloads: Arc::new(AtomicU64::new(0)),
46 runtime_cache: Mutex::new(HashMap::new()),
47 }
48 }
49
50 pub fn cache_get_or_create<T, F>(&self, key: &str, create: F) -> T
56 where
57 T: Clone + Send + Sync + 'static,
58 F: FnOnce() -> T,
59 {
60 let mut guard = self.runtime_cache.lock().unwrap();
61 if let Some(existing) = guard.get(key)
62 && let Some(val) = existing.downcast_ref::<T>()
63 {
64 return val.clone();
65 }
66 let value = create();
67 guard.insert(key.to_string(), Box::new(value.clone()));
68 value
69 }
70
71 pub fn get_or_create_reqwest_client<F>(&self, tag: String, create_client_fn: F) -> crate::error::Result<Client>
80 where
81 F: FnOnce() -> std::result::Result<Client, reqwest::Error>,
82 {
83 let client_cache: Arc<Mutex<Option<(String, Client)>>> =
84 self.cache_get_or_create("global_reqwest_client", || Arc::new(Mutex::new(None)));
85 let mut guard = client_cache.lock()?;
86
87 match guard.as_ref() {
88 Some((cached_tag, cached_client)) if cached_tag == &tag => Ok(cached_client.clone()),
89 _ => {
90 let new_client = create_client_fn()?;
91 *guard = Some((tag, new_client.clone()));
92 Ok(new_client)
93 },
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use std::sync::atomic::{AtomicUsize, Ordering};
101
102 use super::*;
103
104 #[test]
105 fn test_get_or_create_reqwest_client_caches_by_tag() {
106 let common = XetCommon::new(&XetConfig::new());
107 let call_count = AtomicUsize::new(0);
108
109 let _client1 = common
110 .get_or_create_reqwest_client("test-tag".to_string(), || {
111 call_count.fetch_add(1, Ordering::SeqCst);
112 reqwest::Client::builder().build()
113 })
114 .unwrap();
115
116 let _client2 = common
117 .get_or_create_reqwest_client("test-tag".to_string(), || {
118 call_count.fetch_add(1, Ordering::SeqCst);
119 reqwest::Client::builder().build()
120 })
121 .unwrap();
122
123 assert_eq!(call_count.load(Ordering::SeqCst), 1);
124 }
125
126 #[test]
127 fn test_get_or_create_reqwest_client_creates_new_for_different_tag() {
128 let common = XetCommon::new(&XetConfig::new());
129 let call_count = AtomicUsize::new(0);
130
131 let _client1 = common
132 .get_or_create_reqwest_client("tag1".to_string(), || {
133 call_count.fetch_add(1, Ordering::SeqCst);
134 reqwest::Client::builder().user_agent("client1").build()
135 })
136 .unwrap();
137
138 let _client2 = common
139 .get_or_create_reqwest_client("tag2".to_string(), || {
140 call_count.fetch_add(1, Ordering::SeqCst);
141 reqwest::Client::builder().user_agent("client2").build()
142 })
143 .unwrap();
144
145 assert_eq!(call_count.load(Ordering::SeqCst), 2);
146 }
147
148 #[test]
149 fn test_replaces_client_when_tag_changes() {
150 let common = XetCommon::new(&XetConfig::new());
151
152 let _client1 = common
153 .get_or_create_reqwest_client("tcp".to_string(), || {
154 reqwest::Client::builder().user_agent("tcp-client").build()
155 })
156 .unwrap();
157
158 let _client2 = common
159 .get_or_create_reqwest_client("/tmp/socket.sock".to_string(), || {
160 reqwest::Client::builder().user_agent("uds-client").build()
161 })
162 .unwrap();
163
164 let call_count = AtomicUsize::new(0);
167 let _client3 = common
168 .get_or_create_reqwest_client("/tmp/socket.sock".to_string(), || {
169 call_count.fetch_add(1, Ordering::SeqCst);
170 reqwest::Client::builder().build()
171 })
172 .unwrap();
173 assert_eq!(call_count.load(Ordering::SeqCst), 0);
174 }
175
176 #[test]
177 fn test_semaphores_initialized_from_config() {
178 let config = XetConfig::new();
179 let common = XetCommon::new(&config);
180
181 assert_eq!(common.file_ingestion_semaphore.available_permits(), config.data.max_concurrent_file_ingestion);
182 assert_eq!(common.file_download_semaphore.available_permits(), config.data.max_concurrent_file_downloads);
183
184 assert!(
187 common.reconstruction_download_buffer.total_permits()
188 >= config.reconstruction.download_buffer_size.as_u64()
189 );
190
191 assert_eq!(common.active_downloads.load(Ordering::Relaxed), 0);
192 }
193
194 #[test]
195 fn test_cache_get_or_create_returns_cached_value() {
196 let common = XetCommon::new(&XetConfig::new());
197 let call_count = AtomicUsize::new(0);
198
199 let v1: Arc<Mutex<Vec<i32>>> = common.cache_get_or_create("my_cache", || {
200 call_count.fetch_add(1, Ordering::SeqCst);
201 Arc::new(Mutex::new(vec![1, 2, 3]))
202 });
203
204 let v2: Arc<Mutex<Vec<i32>>> = common.cache_get_or_create("my_cache", || {
205 call_count.fetch_add(1, Ordering::SeqCst);
206 Arc::new(Mutex::new(vec![4, 5, 6]))
207 });
208
209 assert_eq!(call_count.load(Ordering::SeqCst), 1);
210 assert!(Arc::ptr_eq(&v1, &v2));
211 }
212
213 #[test]
214 fn test_cache_different_keys_are_independent() {
215 let common = XetCommon::new(&XetConfig::new());
216
217 let v1: Arc<String> = common.cache_get_or_create("key_a", || Arc::new("alpha".to_string()));
218 let v2: Arc<String> = common.cache_get_or_create("key_b", || Arc::new("beta".to_string()));
219
220 assert_eq!(v1.as_str(), "alpha");
221 assert_eq!(v2.as_str(), "beta");
222 }
223
224 #[test]
225 fn test_cache_type_mismatch_creates_new_entry() {
226 let common = XetCommon::new(&XetConfig::new());
227
228 let _v1: Arc<String> = common.cache_get_or_create("shared_key", || Arc::new("original".to_string()));
229
230 let v2: Arc<i32> = common.cache_get_or_create("shared_key", || Arc::new(42));
233 assert_eq!(*v2, 42);
234 }
235}