Skip to main content

apex_sdk/
performance.rs

1//! Performance optimization utilities.
2
3use std::{
4    collections::HashMap,
5    hash::Hash,
6    sync::{Arc, Mutex},
7    time::{Duration, Instant},
8};
9use tokio::sync::Semaphore;
10
11/// Configuration for batch operations
12#[derive(Debug, Clone)]
13pub struct BatchConfig {
14    pub batch_size: usize,
15    pub timeout: Duration,
16}
17
18impl Default for BatchConfig {
19    fn default() -> Self {
20        Self {
21            batch_size: 100,
22            timeout: Duration::from_secs(5),
23        }
24    }
25}
26
27/// Execute multiple operations in batches
28pub async fn batch_execute<T, F, Fut, R>(items: Vec<T>, config: BatchConfig, f: F) -> Vec<R>
29where
30    F: Fn(Vec<T>) -> Fut,
31    Fut: std::future::Future<Output = Vec<R>>,
32    T: Clone,
33{
34    let mut results = Vec::new();
35
36    for chunk in items.chunks(config.batch_size) {
37        let batch_results = tokio::time::timeout(config.timeout, f(chunk.to_vec()))
38            .await
39            .unwrap_or_else(|_| vec![]);
40
41        results.extend(batch_results);
42    }
43
44    results
45}
46
47/// Execute operations in parallel with concurrency control
48pub async fn parallel_execute<T, F, Fut, R>(items: Vec<T>, concurrency: usize, f: F) -> Vec<R>
49where
50    F: Fn(T) -> Fut + Send + Sync,
51    Fut: std::future::Future<Output = R> + Send,
52    T: Send,
53    R: Send,
54{
55    let semaphore = Arc::new(Semaphore::new(concurrency));
56    let f = Arc::new(f);
57
58    let futures = items.into_iter().map(|item| {
59        let semaphore = semaphore.clone();
60        let f = f.clone();
61
62        async move {
63            // Acquire semaphore permit - only fails if semaphore is closed (which shouldn't happen)
64            let _permit = semaphore
65                .acquire()
66                .await
67                .expect("Semaphore should not be closed");
68            f(item).await
69        }
70    });
71
72    futures::future::join_all(futures).await
73}
74
75/// Async memoization cache
76#[derive(Debug, Clone)]
77pub struct AsyncMemo<K, V> {
78    cache: Arc<Mutex<HashMap<K, (V, Instant)>>>,
79    ttl: Option<Duration>,
80}
81
82impl<K: Hash + Eq + Clone, V: Clone> Default for AsyncMemo<K, V> {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88impl<K: Hash + Eq + Clone, V: Clone> AsyncMemo<K, V> {
89    pub fn new() -> Self {
90        Self {
91            cache: Arc::new(Mutex::new(HashMap::new())),
92            ttl: None,
93        }
94    }
95
96    pub fn with_ttl(ttl: Duration) -> Self {
97        Self {
98            cache: Arc::new(Mutex::new(HashMap::new())),
99            ttl: Some(ttl),
100        }
101    }
102
103    pub async fn get_or_compute<F, Fut>(&self, key: K, compute: F) -> V
104    where
105        F: FnOnce() -> Fut,
106        Fut: std::future::Future<Output = V>,
107    {
108        // Check cache first
109        if let Some((value, timestamp)) = self.get_cached(&key) {
110            if let Some(ttl) = self.ttl {
111                if timestamp.elapsed() < ttl {
112                    return value;
113                }
114            } else {
115                return value;
116            }
117        }
118
119        // Compute and cache
120        let value = compute().await;
121        self.insert(key, value.clone());
122        value
123    }
124
125    fn get_cached(&self, key: &K) -> Option<(V, Instant)> {
126        self.cache
127            .lock()
128            .expect("Cache mutex should not be poisoned")
129            .get(key)
130            .cloned()
131    }
132
133    fn insert(&self, key: K, value: V) {
134        self.cache
135            .lock()
136            .expect("Cache mutex should not be poisoned")
137            .insert(key, (value, Instant::now()));
138    }
139
140    pub fn clear(&self) {
141        self.cache
142            .lock()
143            .expect("Cache mutex should not be poisoned")
144            .clear();
145    }
146}
147
148/// Connection pool for managing database/RPC connections
149#[derive(Debug)]
150pub struct ConnectionPool<T> {
151    available: Arc<Semaphore>,
152    max_size: usize,
153    _phantom: std::marker::PhantomData<T>,
154}
155
156impl<T> ConnectionPool<T> {
157    pub fn new(connections: Vec<T>) -> Self {
158        let max_size = connections.len();
159        let available = Arc::new(Semaphore::new(max_size));
160
161        Self {
162            available,
163            max_size,
164            _phantom: std::marker::PhantomData,
165        }
166    }
167
168    pub async fn acquire(&self) -> ConnectionGuard<'_, T> {
169        let permit = self
170            .available
171            .acquire()
172            .await
173            .expect("Connection pool semaphore should not be closed");
174        ConnectionGuard { permit, pool: self }
175    }
176
177    pub fn size(&self) -> usize {
178        self.max_size
179    }
180
181    pub fn available_connections(&self) -> usize {
182        self.available.available_permits()
183    }
184}
185
186/// Guard for connection pool access
187pub struct ConnectionGuard<'a, T> {
188    #[allow(dead_code)]
189    permit: tokio::sync::SemaphorePermit<'a>,
190    #[allow(dead_code)]
191    pool: &'a ConnectionPool<T>,
192}
193
194/// Rate limiter for controlling request rates
195#[derive(Debug)]
196pub struct RateLimiter {
197    semaphore: Arc<Semaphore>,
198    interval: Duration,
199}
200
201impl RateLimiter {
202    pub fn new(max_requests: usize, interval: Duration) -> Self {
203        Self {
204            semaphore: Arc::new(Semaphore::new(max_requests)),
205            interval,
206        }
207    }
208
209    pub async fn acquire(&self) -> RateLimitGuard {
210        let permit = self
211            .semaphore
212            .clone()
213            .acquire_owned()
214            .await
215            .expect("Rate limiter semaphore should not be closed");
216
217        // Schedule permit release after the interval
218        let interval = self.interval;
219
220        tokio::spawn(async move {
221            tokio::time::sleep(interval).await;
222            // Permit is automatically released when dropped
223            drop(permit);
224        });
225
226        RateLimitGuard
227    }
228}
229
230/// Guard for rate limiter
231pub struct RateLimitGuard;
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_connection_pool() {
239        let connections = vec!["conn1", "conn2", "conn3"];
240        let pool = ConnectionPool::new(connections);
241
242        assert_eq!(pool.size(), 3);
243        assert_eq!(pool.available_connections(), 3);
244    }
245
246    #[test]
247    fn test_async_memo() {
248        let _memo = AsyncMemo::<String, i32>::new();
249    }
250
251    #[tokio::test]
252    async fn test_parallel_execute() {
253        let items = vec![1, 2, 3, 4, 5];
254        let results = parallel_execute(
255            items,
256            2, // concurrency
257            |x| async move { x * 2 },
258        )
259        .await;
260
261        assert_eq!(results, vec![2, 4, 6, 8, 10]);
262    }
263
264    #[tokio::test]
265    async fn test_async_memo_with_ttl() {
266        let memo = AsyncMemo::<String, i32>::with_ttl(Duration::from_millis(100));
267
268        let result1 = memo
269            .get_or_compute("key1".to_string(), || async { 42 })
270            .await;
271
272        assert_eq!(result1, 42);
273    }
274
275    #[tokio::test]
276    async fn test_rate_limiter() {
277        let limiter = RateLimiter::new(2, Duration::from_millis(100));
278
279        let _guard1 = limiter.acquire().await;
280        let _guard2 = limiter.acquire().await;
281
282        // Third request should be rate limited
283        let start = Instant::now();
284        let _guard3 = limiter.acquire().await;
285        let elapsed = start.elapsed();
286
287        // Should have waited at least some time (reduced tolerance for CI stability)
288        assert!(elapsed >= Duration::from_millis(50)); // More lenient tolerance for CI
289    }
290}