diff --git a/src/httpcore2/httpcore2/_async/http11.py b/src/httpcore2/httpcore2/_async/http11.py index 769825d9..1248bbff 100644 --- a/src/httpcore2/httpcore2/_async/http11.py +++ b/src/httpcore2/httpcore2/_async/http11.py @@ -251,7 +251,10 @@ def is_available(self) -> bool: def has_expired(self) -> bool: now = time.monotonic() - keepalive_expired = self._expire_at is not None and now > self._expire_at + # Read `_expire_at` once into a local: on free-threaded builds another + # thread may reset it to `None` between the check and the comparison. + expire_at = self._expire_at + keepalive_expired = expire_at is not None and now > expire_at # If the HTTP connection is idle but the socket is readable, then the # only valid state is that the socket is about to return b"", indicating diff --git a/src/httpcore2/httpcore2/_async/http2.py b/src/httpcore2/httpcore2/_async/http2.py index 2c143e07..af5b911a 100644 --- a/src/httpcore2/httpcore2/_async/http2.py +++ b/src/httpcore2/httpcore2/_async/http2.py @@ -484,7 +484,10 @@ def is_available(self) -> bool: def has_expired(self) -> bool: now = time.monotonic() - return self._expire_at is not None and now > self._expire_at + # Read `_expire_at` once into a local: on free-threaded builds another + # thread may reset it to `None` between the check and the comparison. + expire_at = self._expire_at + return expire_at is not None and now > expire_at def is_idle(self) -> bool: return self._state == HTTPConnectionState.IDLE diff --git a/src/httpcore2/httpcore2/_sync/http11.py b/src/httpcore2/httpcore2/_sync/http11.py index 14d674ae..3d6b9d19 100644 --- a/src/httpcore2/httpcore2/_sync/http11.py +++ b/src/httpcore2/httpcore2/_sync/http11.py @@ -251,7 +251,10 @@ def is_available(self) -> bool: def has_expired(self) -> bool: now = time.monotonic() - keepalive_expired = self._expire_at is not None and now > self._expire_at + # Read `_expire_at` once into a local: on free-threaded builds another + # thread may reset it to `None` between the check and the comparison. + expire_at = self._expire_at + keepalive_expired = expire_at is not None and now > expire_at # If the HTTP connection is idle but the socket is readable, then the # only valid state is that the socket is about to return b"", indicating diff --git a/src/httpcore2/httpcore2/_sync/http2.py b/src/httpcore2/httpcore2/_sync/http2.py index 6daf0ac8..457d2a8b 100644 --- a/src/httpcore2/httpcore2/_sync/http2.py +++ b/src/httpcore2/httpcore2/_sync/http2.py @@ -484,7 +484,10 @@ def is_available(self) -> bool: def has_expired(self) -> bool: now = time.monotonic() - return self._expire_at is not None and now > self._expire_at + # Read `_expire_at` once into a local: on free-threaded builds another + # thread may reset it to `None` between the check and the comparison. + expire_at = self._expire_at + return expire_at is not None and now > expire_at def is_idle(self) -> bool: return self._state == HTTPConnectionState.IDLE