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 #[allow(
32 clippy::await_holding_lock,
33 reason = "std Mutex guard dropped before any await; single-threaded executor"
34 )]
35 pub async fn acquire(
36 &self,
37 profile: Option<StealthProfile>,
38 ) -> Result<Page, crate::page::PageError> {
39 let mut pages = self.idle_pages.lock().unwrap_or_else(|e| e.into_inner());
40 if let Some(mut page) = pages.pop_front() {
41 page.reload_html("<html><head></head><body></body></html>", "about:blank");
42 return Ok(page);
43 }
44 Page::from_html("<html><head></head><body></body></html>", profile).await
45 }
46
47 pub fn release(&self, page: Page) {
49 let mut pages = self.idle_pages.lock().unwrap_or_else(|e| e.into_inner());
50 if pages.len() < self.max_size {
51 pages.push_back(page);
52 }
53 }
54
55 pub async fn navigate(
57 &self,
58 url: &str,
59 profile: StealthProfile,
60 ) -> Result<Page, crate::page::PageError> {
61 let mut page = self.acquire(Some(profile)).await?;
62 page.navigate_warm(url).await?;
63 Ok(page)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[tokio::test]
72 async fn pool_acquire_creates_page() {
73 let pool = PagePool::new(4);
74 let page = pool.acquire(None).await;
75 assert!(page.is_ok());
76 }
77
78 #[tokio::test]
79 async fn pool_release_and_reacquire() {
80 let pool = PagePool::new(4);
81 let page = pool.acquire(None).await.unwrap();
82 pool.release(page);
83 let page2 = pool.acquire(None).await;
84 assert!(page2.is_ok());
85 }
86
87 #[tokio::test]
88 async fn pool_respects_max_size() {
89 let pool = PagePool::new(1);
90 let p1 = pool.acquire(None).await.unwrap();
91 let p2 = pool.acquire(None).await.unwrap();
92 pool.release(p1);
93 pool.release(p2); let count = pool.idle_pages.lock().unwrap().len();
95 assert_eq!(count, 1);
96 }
97}