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 use toolkit_gts::gts_id;
214
215 const PLUGIN_A: &str =
216 gts_id!("cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.a.test.plugin.v1");
217 const PLUGIN_B: &str =
218 gts_id!("cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.b.test.plugin.v1");
219 const CONCURRENT_PLUGIN: &str = gts_id!(
220 "cf.toolkit.plugins.plugin.v1~cf.core.test.plugin.v1~vendor.concurrent.test.plugin.v1"
221 );
222
223 #[tokio::test]
224 async fn resolve_called_once_returns_same_str() {
225 let selector = GtsPluginSelector::new();
226 let calls = Arc::new(AtomicUsize::new(0));
227
228 let calls_a = calls.clone();
229 let id_a = selector
230 .get_or_init(|| async move {
231 calls_a.fetch_add(1, Ordering::SeqCst);
232 Ok::<_, std::convert::Infallible>(PLUGIN_A.to_owned())
233 })
234 .await
235 .unwrap();
236
237 let calls_b = calls.clone();
238 let id_b = selector
239 .get_or_init(|| async move {
240 calls_b.fetch_add(1, Ordering::SeqCst);
241 Ok::<_, std::convert::Infallible>(PLUGIN_B.to_owned())
242 })
243 .await
244 .unwrap();
245
246 assert_eq!(id_a, id_b);
247 assert_eq!(calls.load(Ordering::SeqCst), 1);
248 }
249
250 #[tokio::test]
251 async fn reset_triggers_reselection() {
252 let selector = GtsPluginSelector::new();
253 let calls = Arc::new(AtomicUsize::new(0));
254
255 let calls_a = calls.clone();
256 let id_a = selector
257 .get_or_init(|| async move {
258 calls_a.fetch_add(1, Ordering::SeqCst);
259 Ok::<_, std::convert::Infallible>(PLUGIN_A.to_owned())
260 })
261 .await;
262 assert_eq!(&*id_a.unwrap(), PLUGIN_A);
263 assert_eq!(calls.load(Ordering::SeqCst), 1);
264 assert!(selector.reset().await);
265
266 let calls_b = calls.clone();
267 let id_b = selector
268 .get_or_init(|| async move {
269 calls_b.fetch_add(1, Ordering::SeqCst);
270 Ok::<_, std::convert::Infallible>(PLUGIN_B.to_owned())
271 })
272 .await;
273 assert_eq!(&*id_b.unwrap(), PLUGIN_B);
274 assert_eq!(calls.load(Ordering::SeqCst), 2);
275 }
276
277 #[tokio::test]
278 async fn concurrent_get_or_init_resolves_once() {
279 let selector = Arc::new(GtsPluginSelector::new());
280 let calls = Arc::new(AtomicUsize::new(0));
281
282 let mut handles = Vec::new();
283 for _ in 0..10 {
284 let selector = Arc::clone(&selector);
285 let calls = Arc::clone(&calls);
286 handles.push(tokio::spawn(async move {
287 selector
288 .get_or_init(|| async {
289 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
291 calls.fetch_add(1, Ordering::SeqCst);
292 Ok::<_, std::convert::Infallible>(CONCURRENT_PLUGIN.to_owned())
293 })
294 .await
295 }));
296 }
297
298 let mut results = Vec::new();
300 for handle in handles {
301 results.push(handle.await.unwrap().unwrap());
302 }
303
304 for id in &results {
306 assert_eq!(&**id, CONCURRENT_PLUGIN);
307 }
308
309 assert_eq!(calls.load(Ordering::SeqCst), 1);
311 }
312}