From 96fae674fba736f1587bb128f79266a18a906b74 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Thu, 5 Nov 2026 16:23:41 -0700 Subject: [PATCH] Always detach --- solarwinds_apm/oboe/http_sampler.py | 10 +++++---- solarwinds_apm/uams.py | 6 ++++-- tests/unit/test_oboe/test_http_sampler.py | 25 +++++++++++++++++++++++ tests/unit/test_uams.py | 21 ++++++++++++++++++- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/solarwinds_apm/oboe/http_sampler.py b/solarwinds_apm/oboe/http_sampler.py index 58e75e6c0..4f8f4412e 100644 --- a/solarwinds_apm/oboe/http_sampler.py +++ b/solarwinds_apm/oboe/http_sampler.py @@ -149,10 +149,12 @@ def _fetch_from_collector(self): url = f"{self._url}/v1/settings/{self._service}/{self._hostname}" logger.debug("retrieving sampling settings from %s", url) token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) - response = requests.get( - url, headers=self._headers, timeout=REQUEST_TIMEOUT - ) - detach(token) + try: + response = requests.get( + url, headers=self._headers, timeout=REQUEST_TIMEOUT + ) + finally: + detach(token) response.raise_for_status() logger.debug("received sampling settings response %s", response.text) try: diff --git a/solarwinds_apm/uams.py b/solarwinds_apm/uams.py index 8f3a6bac6..6454181b2 100644 --- a/solarwinds_apm/uams.py +++ b/solarwinds_apm/uams.py @@ -61,8 +61,10 @@ def _read_from_api() -> dict: """ try: token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) - response = requests.get(UAMS_CLIENT_URL, timeout=1) - detach(token) + try: + response = requests.get(UAMS_CLIENT_URL, timeout=1) + finally: + detach(token) response.raise_for_status() data = response.json() diff --git a/tests/unit/test_oboe/test_http_sampler.py b/tests/unit/test_oboe/test_http_sampler.py index 9e9c73703..154ada135 100644 --- a/tests/unit/test_oboe/test_http_sampler.py +++ b/tests/unit/test_oboe/test_http_sampler.py @@ -7,6 +7,7 @@ from unittest.mock import MagicMock, patch import pytest +import requests from opentelemetry import trace from opentelemetry.sdk.metrics import AlwaysOnExemplarFilter, MeterProvider from opentelemetry.sdk.metrics._internal.export import InMemoryMetricReader @@ -277,6 +278,30 @@ def test_fetch_from_collector_success(mock_get, config, meter_provider): assert mock_get.call_count == 2 +def test_fetch_from_collector_detaches_context_when_request_fails( + mocker, config, meter_provider +): + mocker.patch.object(HttpSampler, "_loop") + mocker.patch( + "solarwinds_apm.oboe.http_sampler.requests.get", + side_effect=requests.ConnectionError("collector unavailable"), + ) + mock_attach = mocker.patch( + "solarwinds_apm.oboe.http_sampler.attach", return_value="token" + ) + mock_detach = mocker.patch("solarwinds_apm.oboe.http_sampler.detach") + sampler = HttpSampler( + meter_provider=meter_provider, config=config, initial=None + ) + + try: + with pytest.raises(requests.ConnectionError): + sampler._fetch_from_collector() + mock_detach.assert_called_once_with(mock_attach.return_value) + finally: + sampler.shutdown() + + @pytest.mark.parametrize( "json_error", [ diff --git a/tests/unit/test_uams.py b/tests/unit/test_uams.py index 4b509c3f0..ff55ba336 100644 --- a/tests/unit/test_uams.py +++ b/tests/unit/test_uams.py @@ -8,11 +8,16 @@ from unittest.mock import MagicMock, patch import pytest +import requests from opentelemetry.semconv._incubating.attributes.host_attributes import ( HOST_ID, ) -from solarwinds_apm.uams import ATTR_UAMS_CLIENT_ID, UamsResourceDetector +from solarwinds_apm.uams import ( + ATTR_UAMS_CLIENT_ID, + UamsResourceDetector, + _read_from_api, +) UAMS_FILE_ID = str(uuid.uuid4()) UAMS_API_ID = str(uuid.uuid4()) @@ -20,6 +25,20 @@ UAMS_FILE = os.path.join(tempfile.gettempdir(), "uamsclientid") +def test_api_request_failure_detaches_instrumentation_context(mocker): + mocker.patch( + "solarwinds_apm.uams.requests.get", + side_effect=requests.ConnectionError("UAMS unavailable"), + ) + mock_attach = mocker.patch( + "solarwinds_apm.uams.attach", return_value="token" + ) + mock_detach = mocker.patch("solarwinds_apm.uams.detach") + + assert _read_from_api() == {} + mock_detach.assert_called_once_with(mock_attach.return_value) + + @pytest.fixture def setup_file(): os.makedirs(os.path.dirname(UAMS_FILE), exist_ok=True)