Skip to content

Commit 4503dc4

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 dbt-labs#9989
1 parent 34d41ec commit 4503dc4

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import datetime
22
import tempfile
3+
from unittest.mock import MagicMock, patch
34

45
import pytest
56

@@ -79,3 +80,38 @@ def test_disable_never_enabled(self, active_user_none):
7980
def test_initialize_from_flags(self, tempdir, send_anonymous_usage_stats):
8081
dbt.tracking.initialize_from_flags(send_anonymous_usage_stats, tempdir)
8182
assert dbt.tracking.active_user.do_not_track != send_anonymous_usage_stats
83+
84+
85+
class TestTimeoutEmitter:
86+
"""Verify that the TimeoutEmitter uses short timeouts so an unreachable
87+
collector doesn't stall dbt at the end of every invocation.
88+
See https://github.com/dbt-labs/dbt-core/issues/9989
89+
"""
90+
91+
def test_http_post_uses_short_timeout(self):
92+
emitter = dbt.tracking.TimeoutEmitter()
93+
mock_response = MagicMock()
94+
mock_response.status_code = 200
95+
96+
with patch("dbt.tracking.requests.post", return_value=mock_response) as mock_post:
97+
emitter.http_post('{"test": "payload"}')
98+
_, kwargs = mock_post.call_args
99+
assert "timeout" in kwargs
100+
assert kwargs["timeout"] <= 2.0, (
101+
f"POST timeout {kwargs['timeout']}s is too long; keep it ≤ 2s so an unreachable "
102+
"collector doesn't stall dbt at the end of every invocation."
103+
)
104+
105+
def test_http_get_uses_short_timeout(self):
106+
emitter = dbt.tracking.TimeoutEmitter()
107+
mock_response = MagicMock()
108+
mock_response.status_code = 200
109+
110+
with patch("dbt.tracking.requests.get", return_value=mock_response) as mock_get:
111+
emitter.http_get({"test": "payload"})
112+
_, kwargs = mock_get.call_args
113+
assert "timeout" in kwargs
114+
assert kwargs["timeout"] <= 2.0, (
115+
f"GET timeout {kwargs['timeout']}s is too long; keep it ≤ 2s so an unreachable "
116+
"collector doesn't stall dbt at the end of every invocation."
117+
)

0 commit comments

Comments
 (0)