1use std::{
6 collections::VecDeque,
7 sync::{Arc, Mutex},
8};
9
10use crate::{page::Page, stealth::StealthProfile};
11
12pub struct PagePool {
14 idle_pages: Arc<Mutex<VecDeque<Page>>>,
15 max_size: usize,
16}
17
18impl PagePool {
19 #[allow(
20 clippy::arc_with_non_send_sync,
21 reason = "single-threaded page pool; Arc shares idle queue within one thread"
22 )]
23 pub fn new(max_size: usize) -> Self {
24 Self {
25 idle_pages: Arc::new(Mutex::new(VecDeque::with_capacity(max_size))),
26 max_size,
27 }
28 }
29
30 pub async fn acquire(
35 &self,
36 profile: Option<StealthProfile>,
37 ) -> Result<Page, crate::page::PageError> {
38 if let Some(mut page) = {
40 let mut pages = self.idle_pages.lock().unwrap_or_else(|e| e.into_inner());
41 pages.pop_front()
42 } {
43 page.reload_html("<html><head></head><body></body></html>", "about:blank");
44 return Ok(page);
45 }
46 Page::from_html("<html><head></head><body></body></html>", profile.is_some()).await
48 }
49
50 pub fn release(&self, page: Page) {
52 let mut pages = self.idle_pages.lock().unwrap_or_else(|e| e.into_inner());
53 if pages.len() < self.max_size {
54 pages.push_back(page);
55 }
56 }
57
58 pub async fn navigate(
60 &self,
61 url: &str,
62 profile: StealthProfile,
63 ) -> Result<Page, crate::page::PageError> {
64 let mut page = self.acquire(Some(profile)).await?;
65 page.navigate_warm(url).await?;
66 Ok(page)
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[tokio::test]
75 async fn pool_acquire_creates_page() {
76 let pool = PagePool::new(4);
77 let page = pool.acquire(None).await;
78 assert!(page.is_ok());
79 }
80
81 #[tokio::test]
82 async fn pool_release_and_reacquire() {
83 let pool = PagePool::new(4);
84 let page = pool.acquire(None).await.unwrap();
85 pool.release(page);
86 let page2 = pool.acquire(None).await;
87 assert!(page2.is_ok());
88 }
89
90 #[tokio::test]
91 async fn pool_respects_max_size() {
92 let pool = PagePool::new(1);
93 let p1 = pool.acquire(None).await.unwrap();
94 let p2 = pool.acquire(None).await.unwrap();
95 pool.release(p1);
96 pool.release(p2); let count = pool.idle_pages.lock().unwrap().len();
98 assert_eq!(count, 1);
99 }
100}