Process a batch of embedding requests with lock-free concurrency control
(
&self,
batch: EmbeddingBatchRequest,
)
| 809 | |
| 810 | /// Process a batch of embedding requests with lock-free concurrency control |
| 811 | pub async fn process_batch( |
| 812 | &self, |
| 813 | batch: EmbeddingBatchRequest, |
| 814 | ) -> GraphBitResult<EmbeddingBatchResponse> { |
| 815 | let start_time = std::time::Instant::now(); |
| 816 | let max_concurrency = batch.max_concurrency.unwrap_or(self.max_concurrency); |
| 817 | |
| 818 | let mut tasks = Vec::with_capacity(batch.requests.len()); |
| 819 | let current_requests = Arc::clone(&self.current_requests); |
| 820 | |
| 821 | for request in batch.requests { |
| 822 | let config = self.config.clone(); |
| 823 | let current_requests = Arc::clone(¤t_requests); |
| 824 | |
| 825 | let task = tokio::spawn(async move { |
| 826 | // Wait for available slot using atomic operations (no semaphore bottleneck) |
| 827 | loop { |
| 828 | let current = current_requests.load(std::sync::atomic::Ordering::Acquire); |
| 829 | if current < max_concurrency { |
| 830 | match current_requests.compare_exchange( |
| 831 | current, |
| 832 | current + 1, |
| 833 | std::sync::atomic::Ordering::AcqRel, |
| 834 | std::sync::atomic::Ordering::Acquire, |
| 835 | ) { |
| 836 | Ok(_) => break, // Successfully acquired slot |
| 837 | Err(_) => continue, // Retry |
| 838 | } |
| 839 | } |
| 840 | tokio::task::yield_now().await; |
| 841 | } |
| 842 | |
| 843 | // Execute the request |
| 844 | let result = async { |
| 845 | let provider = EmbeddingProviderFactory::create_provider(config)?; |
| 846 | provider.generate_embeddings(request).await |
| 847 | } |
| 848 | .await; |
| 849 | |
| 850 | // Release slot |
| 851 | current_requests.fetch_sub(1, std::sync::atomic::Ordering::AcqRel); |
| 852 | |
| 853 | result |
| 854 | }); |
| 855 | |
| 856 | tasks.push(task); |
| 857 | } |
| 858 | |
| 859 | // Wait for all tasks with optional timeout |
| 860 | let responses = if let Some(timeout_ms) = batch.timeout_ms { |
| 861 | let timeout_duration = tokio::time::Duration::from_millis(timeout_ms); |
| 862 | match tokio::time::timeout(timeout_duration, futures::future::join_all(tasks)).await { |
| 863 | Ok(results) => results, |
| 864 | Err(_) => return Err(GraphBitError::llm("Batch request timed out".to_string())), |
| 865 | } |
| 866 | } else { |
| 867 | futures::future::join_all(tasks).await |
| 868 | }; |