Skip to content

Commit 6000f87

Browse files
authored
feat(transport): Add a timeout (#4252)
For some reason, we don't define any timeouts in our default transport(s). With this change: - We add a 30s total timeout for the whole connect+read cycle in the default HTTP transport - In the experimental HTTP/2 httpcore-based transport there is no way to set a single timeout, so we set 15s each for getting a connection from the pool, connecting, writing, and reading Backend SDKs in general set wildly different timeouts, from 30s in Go to <5s in Ruby or PHP. I went for the higher end of the range here since this is mainly meant to prevent the SDK preventing process shutdown like described in #4247 -- we don't want to cut off legitimate requests that are just taking a long time. (I was considering going even higher, maybe to 60s -- but I think 30s is a good first shot at this and we can always change it later.)
1 parent fb6d374 commit 6000f87

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

sentry_sdk/transport.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ def _parse_rate_limits(header, now=None):
196196
class BaseHttpTransport(Transport):
197197
"""The base HTTP transport."""
198198

199+
TIMEOUT = 30 # seconds
200+
199201
def __init__(self, options):
200202
# type: (Self, Dict[str, Any]) -> None
201203
from sentry_sdk.consts import VERSION
@@ -621,6 +623,7 @@ def _get_pool_options(self):
621623
options = {
622624
"num_pools": 2 if num_pools is None else int(num_pools),
623625
"cert_reqs": "CERT_REQUIRED",
626+
"timeout": urllib3.Timeout(total=self.TIMEOUT),
624627
}
625628

626629
socket_options = None # type: Optional[List[Tuple[int, int, int | bytes]]]
@@ -736,6 +739,8 @@ def __init__(self, options):
736739
class Http2Transport(BaseHttpTransport): # type: ignore
737740
"""The HTTP2 transport based on httpcore."""
738741

742+
TIMEOUT = 15
743+
739744
if TYPE_CHECKING:
740745
_pool: Union[
741746
httpcore.SOCKSProxy, httpcore.HTTPProxy, httpcore.ConnectionPool
@@ -765,6 +770,14 @@ def _request(
765770
self._auth.get_api_url(endpoint_type),
766771
content=body,
767772
headers=headers, # type: ignore
773+
extensions={
774+
"timeout": {
775+
"pool": self.TIMEOUT,
776+
"connect": self.TIMEOUT,
777+
"write": self.TIMEOUT,
778+
"read": self.TIMEOUT,
779+
}
780+
},
768781
)
769782
return response
770783

tests/test_transport.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
from pytest_localserver.http import WSGIServer
1515
from werkzeug.wrappers import Request, Response
1616

17+
try:
18+
import httpcore
19+
except (ImportError, ModuleNotFoundError):
20+
httpcore = None
21+
1722
try:
1823
import gevent
1924
except ImportError:
@@ -274,6 +279,37 @@ def test_keep_alive_on_by_default(make_client):
274279
assert "socket_options" not in options
275280

276281

282+
def test_default_timeout(make_client):
283+
client = make_client()
284+
285+
options = client.transport._get_pool_options()
286+
assert "timeout" in options
287+
assert options["timeout"].total == client.transport.TIMEOUT
288+
289+
290+
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
291+
def test_default_timeout_http2(make_client):
292+
client = make_client(_experiments={"transport_http2": True})
293+
294+
with mock.patch(
295+
"sentry_sdk.transport.httpcore.ConnectionPool.request",
296+
return_value=httpcore.Response(200),
297+
) as request_mock:
298+
sentry_sdk.get_global_scope().set_client(client)
299+
capture_message("hi")
300+
client.flush()
301+
302+
request_mock.assert_called_once()
303+
assert request_mock.call_args.kwargs["extensions"] == {
304+
"timeout": {
305+
"pool": client.transport.TIMEOUT,
306+
"connect": client.transport.TIMEOUT,
307+
"write": client.transport.TIMEOUT,
308+
"read": client.transport.TIMEOUT,
309+
}
310+
}
311+
312+
277313
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
278314
def test_http2_with_https_dsn(make_client):
279315
client = make_client(_experiments={"transport_http2": True})

0 commit comments

Comments
 (0)