Skip to content

Commit a2482eb

Browse files
committed
fix: reduce snowplow telemetry timeout from 5s to 1s
An unreachable snowplow collector previously stalled dbt for up to 5s (or longer if the underlying TCP stack held the connection open) at the end of every invocation. Reduce both the POST and GET timeouts in TimeoutEmitter to 1s so the failure is near-instant for users with no internet access or a firewalled collector endpoint. Closes #9989
1 parent 27f64cf commit a2482eb

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: Fixes
2+
body: Reduce snowplow telemetry HTTP timeout from 5s to 1s so an unreachable collector does not stall dbt at the end of every invocation
3+
time: 2026-03-29T19:00:00.000000-04:00
4+
custom:
5+
Author: claygeo
6+
Issue: "9989"

core/dbt/tracking.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ def http_post(self, payload):
100100
self.endpoint,
101101
data=payload,
102102
headers={"content-type": "application/json; charset=utf-8"},
103-
timeout=5.0,
103+
# Keep the timeout short so that a missing or unreachable collector
104+
# does not noticeably delay the end of every dbt invocation.
105+
# See https://github.com/dbt-labs/dbt-core/issues/9989
106+
timeout=1.0,
104107
)
105108

106109
self._log_result("GET", r.status_code)
@@ -109,7 +112,14 @@ def http_post(self, payload):
109112
def http_get(self, payload):
110113
self._log_request("GET", payload)
111114

112-
r = requests.get(self.endpoint, params=payload, timeout=5.0)
115+
r = requests.get(
116+
self.endpoint,
117+
params=payload,
118+
# Keep the timeout short so that a missing or unreachable collector
119+
# does not noticeably delay the end of every dbt invocation.
120+
# See https://github.com/dbt-labs/dbt-core/issues/9989
121+
timeout=1.0,
122+
)
113123

114124
self._log_result("GET", r.status_code)
115125
return r

tests/unit/test_tracking.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import datetime
22
import tempfile
33
from unittest import mock
4+
from unittest.mock import MagicMock, patch
45

56
import pytest
67

@@ -89,6 +90,7 @@ def test_initialize_from_flags(self, tempdir, send_anonymous_usage_stats):
8990
assert dbt.tracking.active_user.do_not_track != send_anonymous_usage_stats
9091

9192

93+
<<<<<<< HEAD
9294
class TestCompileStatsTracking:
9395
def test_generate_stats_includes_catalog_count(self) -> None:
9496
mock_manifest = mock.MagicMock()
@@ -228,3 +230,38 @@ def test_forwards_catalog_type_from_adapter_integration(
228230
opts = mock_track.call_args[0][0]
229231
assert opts["catalog_type"] == "ICEBERG_REST"
230232
adapter.get_catalog_integration.assert_called_once_with("test_catalog")
233+
234+
235+
class TestTimeoutEmitter:
236+
"""Verify that the TimeoutEmitter uses short timeouts so an unreachable
237+
collector doesn't stall dbt at the end of every invocation.
238+
See https://github.com/dbt-labs/dbt-core/issues/9989
239+
"""
240+
241+
def test_http_post_uses_short_timeout(self):
242+
emitter = dbt.tracking.TimeoutEmitter()
243+
mock_response = MagicMock()
244+
mock_response.status_code = 200
245+
246+
with patch("dbt.tracking.requests.post", return_value=mock_response) as mock_post:
247+
emitter.http_post('{"test": "payload"}')
248+
_, kwargs = mock_post.call_args
249+
assert "timeout" in kwargs
250+
assert kwargs["timeout"] <= 2.0, (
251+
f"POST timeout {kwargs['timeout']}s is too long; keep it ≤ 2s so an unreachable "
252+
"collector doesn't stall dbt at the end of every invocation."
253+
)
254+
255+
def test_http_get_uses_short_timeout(self):
256+
emitter = dbt.tracking.TimeoutEmitter()
257+
mock_response = MagicMock()
258+
mock_response.status_code = 200
259+
260+
with patch("dbt.tracking.requests.get", return_value=mock_response) as mock_get:
261+
emitter.http_get({"test": "payload"})
262+
_, kwargs = mock_get.call_args
263+
assert "timeout" in kwargs
264+
assert kwargs["timeout"] <= 2.0, (
265+
f"GET timeout {kwargs['timeout']}s is too long; keep it ≤ 2s so an unreachable "
266+
"collector doesn't stall dbt at the end of every invocation."
267+
)

0 commit comments

Comments
 (0)