D4Vinci/Scrapling · error · RuntimeError
This FetcherSession instance already has an active synchrono
Error message
This FetcherSession instance already has an active synchronous session.
What it means
Raised by _SyncSessionLogic.__enter__ when you enter the same synchronous session object twice. A _SyncSessionLogic/FetcherSession instance holds exactly one underlying curl_cffi CurlSession and tracks it with the _is_alive flag, so a second __enter__ before __exit__ is rejected. It protects the single curl handle from being clobbered by a nested 'with' on the same instance.
Source
Thrown at scrapling/engines/static.py:204
elif "user-agent" not in headers_keys and not impersonate_enabled: # pragma: no cover
final_headers["User-Agent"] = __default_useragent__
log.debug(f"Can't find useragent in headers so '{final_headers['User-Agent']}' was used.")
return final_headers
class _SyncSessionLogic(_ConfigurationLogic):
__slots__ = ("_curl_session",)
def __init__(self, **kwargs: Unpack[RequestsSession]):
super().__init__(**kwargs)
self._curl_session: Optional[CurlSession] = None
def __enter__(self):
"""Creates and returns a new synchronous Fetcher Session"""
if self._is_alive:
raise RuntimeError("This FetcherSession instance already has an active synchronous session.")
self._curl_session = CurlSession()
self._is_alive = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Closes the active synchronous session managed by this instance, if any."""
# For type checking (not accessed error)
_ = (
exc_type,
exc_val,
exc_tb,
)
if self._curl_session:
self._curl_session.close()
self._curl_session = None
self._is_alive = FalseView on GitHub (pinned to 5d213a2d47)
Solutions
- Use 'with session:' exactly once per instance and do all requests inside that single block.
- If you need nested/concurrent usage, create a separate FetcherSession instance for each context.
- For fire-and-forget requests without context management, use FetcherClient (or the top-level fetch functions), which creates one-off sessions internally.
Example fix
# before
with session:
resp = session.get(url)
with session: # RuntimeError
resp2 = session.get(url2)
# after
with session:
resp = session.get(url)
resp2 = session.get(url2) Defensive patterns
Strategy: validation
Validate before calling
session = FetcherSession(...)
if session._is_alive: # avoid depending on privates in production; prefer structural fix
raise RuntimeError('session already open')
with session as s:
s.get(url) Type guard
def is_session_open(session) -> bool:
return getattr(session, '_is_alive', False) Try / catch
try:
with session:
session.get(url)
except RuntimeError as e:
if 'already has an active' in str(e):
# session was left open: reuse it instead of re-entering
session.get(url)
else:
raise Prevention
- Enter each session exactly once, at the outermost scope that uses it.
- Never share one FetcherSession instance across functions that each open 'with'.
- Use FetcherClient when you don't want lifecycle management at all.
When it happens
Trigger: Calling session.__enter__() manually twice; using 'with session:' while already inside another 'with session:' block on the identical instance; re-entering a FetcherSession's inner client after the outer FetcherSession.__enter__ already created it.
Common situations: Sharing one module-level FetcherSession across functions where one function nests inside another that already opened it; writing a recursive fetch helper that re-opens the session passed as a parameter; reusing a session in a loop that wraps the body in 'with' twice.
Related errors
- No active session available.
- Cannot exit invalid session
- This FetcherSession instance already has an active asynchron
- Session has been already started
- Context manager has been closed
AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14).
Data as JSON: /api/errors/743df858f2ac2a43.
Report an issue: GitHub.