Skip to main content

cortex_runtime/pool/
manager.rs

1//! Pool manager for browser rendering contexts.
2//!
3//! Manages a pool of browser contexts, controlling concurrency
4//! and reusing contexts when possible.
5
6use crate::renderer::{RenderContext, Renderer};
7use anyhow::Result;
8use std::sync::atomic::{AtomicUsize, Ordering};
9use std::sync::Arc;
10use tokio::sync::Semaphore;
11
12/// Handle to a borrowed browser context from the pool.
13pub struct ContextHandle {
14    context: Option<Box<dyn RenderContext>>,
15    _permit: tokio::sync::OwnedSemaphorePermit,
16    active_count: Arc<AtomicUsize>,
17}
18
19impl ContextHandle {
20    /// Get a reference to the render context.
21    pub fn context(&self) -> &dyn RenderContext {
22        self.context
23            .as_ref()
24            .expect("context already taken")
25            .as_ref()
26    }
27
28    /// Get a mutable reference to the render context.
29    pub fn context_mut(&mut self) -> &mut dyn RenderContext {
30        self.context
31            .as_mut()
32            .expect("context already taken")
33            .as_mut()
34    }
35
36    /// Take the context out of the handle (for passing to close).
37    pub fn take(mut self) -> Box<dyn RenderContext> {
38        self.context.take().expect("context already taken")
39    }
40}
41
42impl Drop for ContextHandle {
43    fn drop(&mut self) {
44        self.active_count.fetch_sub(1, Ordering::SeqCst);
45    }
46}
47
48/// Manages a pool of browser contexts with concurrency limits.
49pub struct PoolManager {
50    renderer: Arc<dyn Renderer>,
51    semaphore: Arc<Semaphore>,
52    max_contexts: usize,
53    active_count: Arc<AtomicUsize>,
54}
55
56impl PoolManager {
57    /// Create a new pool manager.
58    pub fn new(renderer: Arc<dyn Renderer>, max_contexts: usize) -> Self {
59        Self {
60            renderer,
61            semaphore: Arc::new(Semaphore::new(max_contexts)),
62            max_contexts,
63            active_count: Arc::new(AtomicUsize::new(0)),
64        }
65    }
66
67    /// Acquire a browser context from the pool.
68    ///
69    /// Blocks if the maximum number of concurrent contexts is reached.
70    pub async fn acquire(&self) -> Result<ContextHandle> {
71        let permit = Arc::clone(&self.semaphore)
72            .acquire_owned()
73            .await
74            .map_err(|e| anyhow::anyhow!("semaphore closed: {}", e))?;
75
76        let context = self.renderer.new_context().await?;
77        self.active_count.fetch_add(1, Ordering::SeqCst);
78
79        Ok(ContextHandle {
80            context: Some(context),
81            _permit: permit,
82            active_count: Arc::clone(&self.active_count),
83        })
84    }
85
86    /// Return a context to the pool (closes it).
87    pub async fn release(&self, handle: ContextHandle) -> Result<()> {
88        let context = handle.take();
89        context.close().await
90    }
91
92    /// Number of currently active contexts.
93    pub fn active(&self) -> usize {
94        self.active_count.load(Ordering::SeqCst)
95    }
96
97    /// Maximum allowed concurrent contexts.
98    pub fn max_contexts(&self) -> usize {
99        self.max_contexts
100    }
101
102    /// Available permits (slots for new contexts).
103    pub fn available(&self) -> usize {
104        self.semaphore.available_permits()
105    }
106}