1use std::future::Future;
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5use tokio::sync::Mutex;
6
7use crate::gts::PluginV1;
8
9pub struct GtsPluginSelector {
15 cached: RwLock<Option<Arc<str>>>,
17 resolve_lock: Mutex<()>,
19}
20
21impl Default for GtsPluginSelector {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl GtsPluginSelector {
28 #[must_use]
29 pub fn new() -> Self {
30 Self {
31 cached: RwLock::new(None),
32 resolve_lock: Mutex::new(()),
33 }
34 }
35
36 #[must_use]
41 pub fn pre_cached(value: String) -> Self {
42 Self {
43 cached: RwLock::new(Some(Arc::from(value))),
44 resolve_lock: Mutex::new(()),
45 }
46 }
47
48 pub async fn get_or_init<F, Fut, E>(&self, resolve: F) -> Result<Arc<str>, E>
57 where
58 F: FnOnce() -> Fut,
59 Fut: Future<Output = Result<String, E>>,
60 {
61 {
63 let guard = self.cached.read();
64 if let Some(ref id) = *guard {
65 return Ok(Arc::clone(id));
66 }
67 }
68
69 let _resolve_guard = self.resolve_lock.lock().await;
71
72 {
74 let guard = self.cached.read();
75 if let Some(ref id) = *guard {
76 return Ok(Arc::clone(id));
77 }
78 }
79
80 let id_string = resolve().await?;
82 let id: Arc<str> = id_string.into();
83
84 {
85 let mut guard = self.cached.write();
86 *guard = Some(Arc::clone(&id));
87 }
88
89 Ok(id)
90 }
91
92 pub async fn reset(&self) -> bool {
96 let _resolve_guard = self.resolve_lock.lock().await;
97 let mut guard = self.cached.write();
98 guard.take().is_some()
99 }
100}
101
102#[derive(Debug, thiserror::Error)]
104pub enum ChoosePluginError {
105 #[error("invalid plugin instance content for '{gts_id}': {reason}")]
107 InvalidPluginInstance {
108 gts_id: String,
110 reason: String,
112 },
113
114 #[error("no plugin instances found for type '{type_id}', vendor '{vendor}'")]
116 PluginNotFound {
117 type_id: String,
119 vendor: String,
121 },
122}
123
124pub fn choose_plugin_instance<'a, P>(
151 vendor: &str,
152 instances: impl IntoIterator<Item = (&'a str, &'a serde_json::Value)>,
153) -> Result<String, ChoosePluginError>
154where
155 P: for<'de> gts::GtsDeserialize<'de> + gts::GtsSchema,
156{
157 let mut best: Option<(&str, i16)> = None;
158 let mut count: usize = 0;
159
160 for (gts_id, content_val) in instances {
161 count += 1;
162 let content: PluginV1<P> = serde_json::from_value(content_val.clone()).map_err(|e| {
163 tracing::error!(
164 gts_id = %gts_id,
165 error = %e,
166 "Failed to deserialize plugin instance content"
167 );
168 ChoosePluginError::InvalidPluginInstance {
169 gts_id: gts_id.to_owned(),
170 reason: e.to_string(),
171 }
172 })?;
173
174 if content.id != gts_id {
175 return Err(ChoosePluginError::InvalidPluginInstance {
176 gts_id: gts_id.to_owned(),
177 reason: format!(
178 "content.id mismatch: expected {:?}, got {:?}",
179 gts_id, content.id
180 ),
181 });
182 }
183
184 if content.vendor != vendor {
185 continue;
186 }
187
188 match &best {
189 None => best = Some((gts_id, content.priority)),
190 Some((_, cur_priority)) => {
191 if content.priority < *cur_priority {
192 best = Some((gts_id, content.priority));
193 }
194 }
195 }
196 }
197
198 tracing::debug!(vendor, instance_count = count, "choose_plugin_instance");
199
200 best.map(|(gts_id, _)| gts_id.to_owned())
201 .ok_or_else(|| ChoosePluginError::PluginNotFound {
202 type_id: P::TYPE_ID.to_owned(),
203 vendor: vendor.to_owned(),
204 })
205}
206
207#[cfg(test)]
208#[cfg_attr(coverage_nightly, coverage(off))]
209mod tests {
210 use super::*;
211 use std::sync::Arc;
212 use std::sync::atomic::{AtomicUsize, Ordering};
213
214 #[tokio::test]
215 async fn resolve_called_once_returns_same_str() {
216 let selector = GtsPluginSelector::new();
217 let calls = Arc::new(AtomicUsize::new(0));
218
219 let calls_a = calls.clone();
220 let id_a = selector
221 .get_or_init(|| async move {
222 calls_a.fetch_add(1, Ordering::SeqCst);
223 Ok::<_, std::convert::Infallible>(
224 "gts.cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.a.test.plugin.v1"
225 .to_owned(),
226 )
227 })
228 .await
229 .unwrap();
230
231 let calls_b = calls.clone();
232 let id_b = selector
233 .get_or_init(|| async move {
234 calls_b.fetch_add(1, Ordering::SeqCst);
235 Ok::<_, std::convert::Infallible>(
236 "gts.cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.b.test.plugin.v1"
237 .to_owned(),
238 )
239 })
240 .await
241 .unwrap();
242
243 assert_eq!(id_a, id_b);
244 assert_eq!(calls.load(Ordering::SeqCst), 1);
245 }
246
247 #[tokio::test]
248 async fn reset_triggers_reselection() {
249 let selector = GtsPluginSelector::new();
250 let calls = Arc::new(AtomicUsize::new(0));
251
252 let calls_a = calls.clone();
253 let id_a = selector
254 .get_or_init(|| async move {
255 calls_a.fetch_add(1, Ordering::SeqCst);
256 Ok::<_, std::convert::Infallible>(
257 "gts.cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.a.test.plugin.v1"
258 .to_owned(),
259 )
260 })
261 .await;
262 assert_eq!(
263 &*id_a.unwrap(),
264 "gts.cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.a.test.plugin.v1"
265 );
266 assert_eq!(calls.load(Ordering::SeqCst), 1);
267 assert!(selector.reset().await);
268
269 let calls_b = calls.clone();
270 let id_b = selector
271 .get_or_init(|| async move {
272 calls_b.fetch_add(1, Ordering::SeqCst);
273 Ok::<_, std::convert::Infallible>(
274 "gts.cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.b.test.plugin.v1"
275 .to_owned(),
276 )
277 })
278 .await;
279 assert_eq!(
280 &*id_b.unwrap(),
281 "gts.cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.b.test.plugin.v1"
282 );
283 assert_eq!(calls.load(Ordering::SeqCst), 2);
284 }
285
286 #[tokio::test]
287 async fn concurrent_get_or_init_resolves_once() {
288 let selector = Arc::new(GtsPluginSelector::new());
289 let calls = Arc::new(AtomicUsize::new(0));
290
291 let mut handles = Vec::new();
292 for _ in 0..10 {
293 let selector = Arc::clone(&selector);
294 let calls = Arc::clone(&calls);
295 handles.push(tokio::spawn(async move {
296 selector
297 .get_or_init(|| async {
298 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
300 calls.fetch_add(1, Ordering::SeqCst);
301 Ok::<_, std::convert::Infallible>(
302 "gts.cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.concurrent.test.plugin.v1"
303 .to_owned(),
304 )
305 })
306 .await
307 }));
308 }
309
310 let mut results = Vec::new();
312 for handle in handles {
313 results.push(handle.await.unwrap().unwrap());
314 }
315
316 for id in &results {
318 assert_eq!(
319 &**id,
320 "gts.cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.concurrent.test.plugin.v1"
321 );
322 }
323
324 assert_eq!(calls.load(Ordering::SeqCst), 1);
326 }
327}