Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/Fixes-20260329-190000.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Fixes
body: Reduce snowplow telemetry HTTP timeout from 5s to 1s so an unreachable collector does not stall dbt at the end of every invocation
time: 2026-03-29T19:00:00.000000-04:00
custom:
Author: claygeo
Issue: "9989"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changelog looks extraneous given the one above that references #9989

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Fixes
body: "Reduce snowplow telemetry connection timeout from 5s to 1s"
time: 2026-03-30T15:27:05.000000+00:00
custom:
Author: claygeo
Issue: "12741"
14 changes: 12 additions & 2 deletions core/dbt/tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ def http_post(self, payload):
self.endpoint,
data=payload,
headers={"content-type": "application/json; charset=utf-8"},
timeout=5.0,
# Keep the timeout short so that a missing or unreachable collector
# does not noticeably delay the end of every dbt invocation.
# See https://github.com/dbt-labs/dbt-core/issues/9989
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

timeout=1.0,
)

self._log_result("GET", r.status_code)
Expand All @@ -109,7 +112,14 @@ def http_post(self, payload):
def http_get(self, payload):
self._log_request("GET", payload)

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

self._log_result("GET", r.status_code)
return r
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/test_tracking.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import tempfile
from unittest import mock
from unittest.mock import MagicMock, patch

import pytest

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


<<<<<<< HEAD
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks unintentional

class TestCompileStatsTracking:
def test_generate_stats_includes_catalog_count(self) -> None:
mock_manifest = mock.MagicMock()
Expand Down Expand Up @@ -228,3 +230,38 @@ def test_forwards_catalog_type_from_adapter_integration(
opts = mock_track.call_args[0][0]
assert opts["catalog_type"] == "ICEBERG_REST"
adapter.get_catalog_integration.assert_called_once_with("test_catalog")


class TestTimeoutEmitter:
"""Verify that the TimeoutEmitter uses short timeouts so an unreachable
collector doesn't stall dbt at the end of every invocation.
See https://github.com/dbt-labs/dbt-core/issues/9989
"""

def test_http_post_uses_short_timeout(self):
emitter = dbt.tracking.TimeoutEmitter()
mock_response = MagicMock()
mock_response.status_code = 200

with patch("dbt.tracking.requests.post", return_value=mock_response) as mock_post:
emitter.http_post('{"test": "payload"}')
_, kwargs = mock_post.call_args
assert "timeout" in kwargs
assert kwargs["timeout"] <= 2.0, (
f"POST timeout {kwargs['timeout']}s is too long; keep it ≤ 2s so an unreachable "
"collector doesn't stall dbt at the end of every invocation."
)

def test_http_get_uses_short_timeout(self):
emitter = dbt.tracking.TimeoutEmitter()
mock_response = MagicMock()
mock_response.status_code = 200

with patch("dbt.tracking.requests.get", return_value=mock_response) as mock_get:
emitter.http_get({"test": "payload"})
_, kwargs = mock_get.call_args
assert "timeout" in kwargs
assert kwargs["timeout"] <= 2.0, (
f"GET timeout {kwargs['timeout']}s is too long; keep it ≤ 2s so an unreachable "
"collector doesn't stall dbt at the end of every invocation."
)
Loading