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 = False

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Use 'with session:' exactly once per instance and do all requests inside that single block.
  2. If you need nested/concurrent usage, create a separate FetcherSession instance for each context.
  3. 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

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


AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14). Data as JSON: /api/errors/743df858f2ac2a43. Report an issue: GitHub.