D4Vinci/Scrapling · error · RuntimeError
Maximum page limit ({self.max_pages}) reached
Error message
Maximum page limit ({self.max_pages}) reached What it means
Raised by `PagePool.add_page` (scrapling/engines/_browsers/_page.py:61) when trying to register a new browser page after the pool already holds `max_pages` entries. The pool caps concurrent pages per session; `add_page` is called internally when `_get_page` creates a page, so the RuntimeError surfaces when the pool is full — normally prevented upstream by the wait logic in `_base.py` that pauses until a page frees (see error 29) or by sizing `max_pages` to your workload.
Source
Thrown at scrapling/engines/_browsers/_page.py:61
__slots__ = ("max_pages", "pages", "_lock")
def __init__(self, max_pages: int = 5):
self.max_pages = max_pages
self.pages: List[PageInfo[SyncPage] | PageInfo[AsyncPage]] = []
self._lock = RLock()
@overload
def add_page(self, page: SyncPage) -> PageInfo[SyncPage]: ...
@overload
def add_page(self, page: AsyncPage) -> PageInfo[AsyncPage]: ...
def add_page(self, page: SyncPage | AsyncPage) -> PageInfo[SyncPage] | PageInfo[AsyncPage]:
"""Add a new page to the pool"""
with self._lock:
if len(self.pages) >= self.max_pages:
raise RuntimeError(f"Maximum page limit ({self.max_pages}) reached")
if isinstance(page, AsyncPage):
page_info: PageInfo[SyncPage] | PageInfo[AsyncPage] = cast(
PageInfo[AsyncPage], PageInfo(page, "ready", "")
)
else:
page_info = cast(PageInfo[SyncPage], PageInfo(page, "ready", ""))
self.pages.append(page_info)
return page_info
@property
def pages_count(self) -> int:
"""Get the total number of pages"""
return len(self.pages)
@property
def busy_count(self) -> int:View on GitHub (pinned to 5d213a2d47)
Solutions
- Size `max_pages` at or above your maximum concurrency: `DynamicSession(max_pages=n_workers)`.
- Ensure pages are released/closed promptly after each fetch so the pool drains.
- Bound callers with a semaphore matching `max_pages`.
- Catch RuntimeError around fetch and re-queue the URL instead of aborting the batch.
Example fix
# before
with DynamicSession(max_pages=2) as s:
results = await asyncio.gather(*[s.fetch(u) for u in urls[:10]]) # >2 concurrent -> RuntimeError
# after
with DynamicSession(max_pages=10) as s:
results = await asyncio.gather(*[s.fetch(u) for u in urls[:10]]) Defensive patterns
Strategy: validation
Validate before calling
def page_pool_has_capacity(session, extra: int = 1) -> bool:
pool = session.page_pool
return pool.pages_count + extra <= pool.max_pages Try / catch
try:
resp = session.fetch(url)
except RuntimeError as e:
if 'Maximum page limit' in str(e):
await asyncio.sleep(1) # let pages free up, then retry
resp = session.fetch(url)
else:
raise Prevention
- Set max_pages at session creation to your max concurrency.
- Close/release pages promptly after each fetch.
- Cap callers with a semaphore sized to max_pages.
When it happens
Trigger: More pages created concurrently than `max_pages` in one session — e.g. `max_pages=3` while 5 threads/tasks fetch simultaneously and the pool-wait path is bypassed (explicit `context=` provided, so no wait), or direct `session.page_pool.add_page(page)` calls exceeding the cap.
Common situations: Thread/asyncio worker count above `max_pages`; batch jobs where each task opens a page without closing prior ones; forgetting that proxy-rotation mode creates extra contexts whose pages also enter the pool.
Related errors
- No pages finished to clear place in the pool within the {sel
- Browser not initialized for proxy rotation mode
- Session has been already started
- Context manager has been closed
- Failed to get response for {url}
AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14).
Data as JSON: /api/errors/b6148dc9686a9775.
Report an issue: GitHub.