Skip to content

Commit 370af5f

Browse files
authored
Bug fix: detect and adapt to backoff package version (#2980)
1 parent 11f49d5 commit 370af5f

File tree

14 files changed

+182
-19
lines changed

14 files changed

+182
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
([#2976](https://github.com/open-telemetry/opentelemetry-python/pull/2976))
2020
- [exporter/opentelemetry-exporter-otlp-proto-http] Add OTLPMetricExporter
2121
([#2891](https://github.com/open-telemetry/opentelemetry-python/pull/2891))
22+
- Fix a bug with exporter retries for with newer versions of the backoff library
23+
([#2980](https://github.com/open-telemetry/opentelemetry-python/pull/2980))
2224

2325
## [1.13.0-0.34b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.13.0) - 2022-09-26
2426

@@ -90,7 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9092
([#2726](https://github.com/open-telemetry/opentelemetry-python/pull/2726))
9193
- fix: frozenset object has no attribute items
9294
([#2727](https://github.com/open-telemetry/opentelemetry-python/pull/2727))
93-
- fix: create suppress HTTP instrumentation key in opentelemetry context
95+
- fix: create suppress HTTP instrumentation key in opentelemetry context
9496
([#2729](https://github.com/open-telemetry/opentelemetry-python/pull/2729))
9597
- Support logs SDK auto instrumentation enable/disable with env
9698
([#2728](https://github.com/open-telemetry/opentelemetry-python/pull/2728))

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from urllib.parse import urlparse
2626
from opentelemetry.sdk.trace import ReadableSpan
2727

28-
from backoff import expo
28+
import backoff
2929
from google.rpc.error_details_pb2 import RetryInfo
3030
from grpc import (
3131
ChannelCredentials,
@@ -183,6 +183,19 @@ def _get_credentials(creds, environ_key):
183183
return ssl_channel_credentials()
184184

185185

186+
# Work around API change between backoff 1.x and 2.x. Since 2.0.0 the backoff
187+
# wait generator API requires a first .send(None) before reading the backoff
188+
# values from the generator.
189+
_is_backoff_v2 = next(backoff.expo()) is None
190+
191+
192+
def _expo(*args, **kwargs):
193+
gen = backoff.expo(*args, **kwargs)
194+
if _is_backoff_v2:
195+
gen.send(None)
196+
return gen
197+
198+
186199
# pylint: disable=no-member
187200
class OTLPExporterMixin(
188201
ABC, Generic[SDKDataT, ExportServiceRequestT, ExportResultT]
@@ -296,7 +309,7 @@ def _export(
296309
# expo returns a generator that yields delay values which grow
297310
# exponentially. Once delay is greater than max_value, the yielded
298311
# value will remain constant.
299-
for delay in expo(max_value=max_value):
312+
for delay in _expo(max_value=max_value):
300313

301314
if delay == max_value:
302315
return self._result.FAILURE

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def test_otlp_exporter_endpoint(self, mock_secure, mock_insecure):
251251
)
252252
mock_method.reset_mock()
253253

254-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.expo")
254+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
255255
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
256256
def test_unavailable(self, mock_sleep, mock_expo):
257257

@@ -265,7 +265,7 @@ def test_unavailable(self, mock_sleep, mock_expo):
265265
)
266266
mock_sleep.assert_called_with(1)
267267

268-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.expo")
268+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
269269
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
270270
def test_unavailable_delay(self, mock_sleep, mock_expo):
271271

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/metrics/test_otlp_metrics_exporter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ def test_otlp_exporter_endpoint(self, mock_secure, mock_insecure):
449449
)
450450
mock_method.reset_mock()
451451

452-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.expo")
452+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
453453
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
454454
def test_unavailable(self, mock_sleep, mock_expo):
455455

@@ -464,7 +464,7 @@ def test_unavailable(self, mock_sleep, mock_expo):
464464
)
465465
mock_sleep.assert_called_with(1)
466466

467-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.expo")
467+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
468468
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
469469
def test_unavailable_delay(self, mock_sleep, mock_expo):
470470

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def test_environ_to_compression(self):
5656
with self.assertRaises(InvalidCompressionValueException):
5757
environ_to_compression("test_invalid")
5858

59-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.expo")
59+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
6060
def test_export_warning(self, mock_expo):
6161

6262
mock_expo.configure_mock(**{"return_value": [0]})

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,23 @@ def test_otlp_headers(self, mock_ssl_channel, mock_secure):
436436
# pylint: disable=protected-access
437437
self.assertIsNone(exporter._headers, None)
438438

439-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.expo")
439+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.backoff")
440+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
441+
def test_handles_backoff_v2_api(self, mock_sleep, mock_backoff):
442+
# In backoff ~= 2.0.0 the first value yielded from expo is None.
443+
def generate_delays(*args, **kwargs):
444+
yield None
445+
yield 1
446+
447+
mock_backoff.expo.configure_mock(**{"side_effect": generate_delays})
448+
449+
add_TraceServiceServicer_to_server(
450+
TraceServiceServicerUNAVAILABLE(), self.server
451+
)
452+
self.exporter.export([self.span])
453+
mock_sleep.assert_called_once_with(1)
454+
455+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
440456
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
441457
def test_unavailable(self, mock_sleep, mock_expo):
442458

@@ -450,7 +466,7 @@ def test_unavailable(self, mock_sleep, mock_expo):
450466
)
451467
mock_sleep.assert_called_with(1)
452468

453-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.expo")
469+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
454470
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
455471
def test_unavailable_delay(self, mock_sleep, mock_expo):
456472

exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ dependencies = [
3434
]
3535

3636
[project.optional-dependencies]
37-
test = []
37+
test = [
38+
"responses == 0.22.0",
39+
]
3840

3941
[project.entry-points.opentelemetry_traces_exporter]
4042
otlp_proto_http = "opentelemetry.exporter.otlp.proto.http.trace_exporter:OTLPSpanExporter"

exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
from typing import Dict, Optional, Sequence
2121
from time import sleep
2222

23+
import backoff
2324
import requests
24-
from backoff import expo
2525

2626
from opentelemetry.sdk.environment_variables import (
2727
OTEL_EXPORTER_OTLP_CERTIFICATE,
@@ -53,6 +53,18 @@
5353
DEFAULT_LOGS_EXPORT_PATH = "v1/logs"
5454
DEFAULT_TIMEOUT = 10 # in seconds
5555

56+
# Work around API change between backoff 1.x and 2.x. Since 2.0.0 the backoff
57+
# wait generator API requires a first .send(None) before reading the backoff
58+
# values from the generator.
59+
_is_backoff_v2 = next(backoff.expo()) is None
60+
61+
62+
def _expo(*args, **kwargs):
63+
gen = backoff.expo(*args, **kwargs)
64+
if _is_backoff_v2:
65+
gen.send(None)
66+
return gen
67+
5668

5769
class OTLPLogExporter(LogExporter):
5870

@@ -122,7 +134,7 @@ def export(self, batch: Sequence[LogData]) -> LogExportResult:
122134

123135
serialized_data = _ProtobufEncoder.serialize(batch)
124136

125-
for delay in expo(max_value=self._MAX_RETRY_TIMEOUT):
137+
for delay in _expo(max_value=self._MAX_RETRY_TIMEOUT):
126138

127139
if delay == self._MAX_RETRY_TIMEOUT:
128140
return LogExportResult.FAILURE

exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@
6666
from opentelemetry.sdk.resources import Resource as SDKResource
6767
from opentelemetry.util.re import parse_headers
6868

69+
import backoff
6970
import requests
70-
from backoff import expo
7171

7272
_logger = logging.getLogger(__name__)
7373

@@ -77,6 +77,18 @@
7777
DEFAULT_METRICS_EXPORT_PATH = "v1/metrics"
7878
DEFAULT_TIMEOUT = 10 # in seconds
7979

80+
# Work around API change between backoff 1.x and 2.x. Since 2.0.0 the backoff
81+
# wait generator API requires a first .send(None) before reading the backoff
82+
# values from the generator.
83+
_is_backoff_v2 = next(backoff.expo()) is None
84+
85+
86+
def _expo(*args, **kwargs):
87+
gen = backoff.expo(*args, **kwargs)
88+
if _is_backoff_v2:
89+
gen.send(None)
90+
return gen
91+
8092

8193
class OTLPMetricExporter(MetricExporter):
8294

@@ -319,7 +331,7 @@ def export(
319331
**kwargs,
320332
) -> MetricExportResult:
321333
serialized_data = self._translate_data(metrics_data)
322-
for delay in expo(max_value=self._MAX_RETRY_TIMEOUT):
334+
for delay in _expo(max_value=self._MAX_RETRY_TIMEOUT):
323335

324336
if delay == self._MAX_RETRY_TIMEOUT:
325337
return MetricExportResult.FAILURE

exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
from typing import Dict, Optional
2121
from time import sleep
2222

23+
import backoff
2324
import requests
24-
from backoff import expo
2525

2626
from opentelemetry.sdk.environment_variables import (
2727
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE,
@@ -54,6 +54,18 @@
5454
DEFAULT_TRACES_EXPORT_PATH = "v1/traces"
5555
DEFAULT_TIMEOUT = 10 # in seconds
5656

57+
# Work around API change between backoff 1.x and 2.x. Since 2.0.0 the backoff
58+
# wait generator API requires a first .send(None) before reading the backoff
59+
# values from the generator.
60+
_is_backoff_v2 = next(backoff.expo()) is None
61+
62+
63+
def _expo(*args, **kwargs):
64+
gen = backoff.expo(*args, **kwargs)
65+
if _is_backoff_v2:
66+
gen.send(None)
67+
return gen
68+
5769

5870
class OTLPSpanExporter(SpanExporter):
5971

@@ -133,7 +145,7 @@ def export(self, spans) -> SpanExportResult:
133145

134146
serialized_data = _ProtobufEncoder.serialize(spans)
135147

136-
for delay in expo(max_value=self._MAX_RETRY_TIMEOUT):
148+
for delay in _expo(max_value=self._MAX_RETRY_TIMEOUT):
137149

138150
if delay == self._MAX_RETRY_TIMEOUT:
139151
return SpanExportResult.FAILURE

0 commit comments

Comments
 (0)