Skip to content

Commit 8b50ddd

Browse files
committed
Detect and adapt to backoff package version
Since backoff==2.0.0, the API of the expo function has changed. The "porcelain" methods of the backoff library (the decorators backoff.on_exception and backoff.on_predicate) send a "None" value into the generator and discard the first yielded value. This is for compatibility with the more general wait generator API inside the backoff package. This commit allows the OTLP exporters to automatically detect the behavior of the installed backoff package and adapt accordingly.
1 parent 6453a32 commit 8b50ddd

File tree

8 files changed

+55
-13
lines changed

8 files changed

+55
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
([#2959](https://github.com/open-telemetry/opentelemetry-python/pull/2959))
1414
- Add http-metric instrument names to semantic conventions
1515
([#2976](https://github.com/open-telemetry/opentelemetry-python/pull/2976))
16+
- Fix a bug with exporter retries for with newer versions of the backoff library
17+
([#2980](https://github.com/open-telemetry/opentelemetry-python/pull/2980))
1618

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

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

Lines changed: 16 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,20 @@ 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+
if next(backoff.expo()) is None:
190+
191+
def _expo(*args, **kwargs):
192+
gen = backoff.expo(*args, **kwargs)
193+
gen.send(None)
194+
return gen
195+
196+
else:
197+
_expo = backoff.expo
198+
199+
186200
# pylint: disable=no-member
187201
class OTLPExporterMixin(
188202
ABC, Generic[SDKDataT, ExportServiceRequestT, ExportResultT]
@@ -296,7 +310,7 @@ def _export(
296310
# expo returns a generator that yields delay values which grow
297311
# exponentially. Once delay is greater than max_value, the yielded
298312
# value will remain constant.
299-
for delay in expo(max_value=max_value):
313+
for delay in _expo(max_value=max_value):
300314

301315
if delay == max_value:
302316
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ 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._expo")
440440
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
441441
def test_unavailable(self, mock_sleep, mock_expo):
442442

@@ -450,7 +450,7 @@ def test_unavailable(self, mock_sleep, mock_expo):
450450
)
451451
mock_sleep.assert_called_with(1)
452452

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

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

Lines changed: 15 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,19 @@
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+
if next(backoff.expo()) is None:
60+
61+
def _expo(*args, **kwargs):
62+
gen = backoff.expo(*args, **kwargs)
63+
gen.send(None)
64+
return gen
65+
66+
else:
67+
_expo = backoff.expo
68+
5669

5770
class OTLPLogExporter(LogExporter):
5871

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

123136
serialized_data = _ProtobufEncoder.serialize(batch)
124137

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

127140
if delay == self._MAX_RETRY_TIMEOUT:
128141
return LogExportResult.FAILURE

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

Lines changed: 15 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,19 @@
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+
if next(backoff.expo()) is None:
61+
62+
def _expo(*args, **kwargs):
63+
gen = backoff.expo(*args, **kwargs)
64+
gen.send(None)
65+
return gen
66+
67+
else:
68+
_expo = backoff.expo
69+
5770

5871
class OTLPSpanExporter(SpanExporter):
5972

@@ -133,7 +146,7 @@ def export(self, spans) -> SpanExportResult:
133146

134147
serialized_data = _ProtobufEncoder.serialize(spans)
135148

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

138151
if delay == self._MAX_RETRY_TIMEOUT:
139152
return SpanExportResult.FAILURE

0 commit comments

Comments
 (0)