Skip to content

Commit 9121382

Browse files
pmcollinsocelotl
andauthored
Factor out duplicate backoff code (#3396)
Co-authored-by: Diego Hurtado <[email protected]>
1 parent 0fa0ce5 commit 9121382

File tree

13 files changed

+69
-76
lines changed

13 files changed

+69
-76
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ classifiers = [
2626
]
2727
dependencies = [
2828
"opentelemetry-proto == 1.20.0.dev",
29+
"backoff >= 1.10.0, < 2.0.0; python_version<'3.7'",
30+
"backoff >= 1.10.0, < 3.0.0; python_version>='3.7'",
2931
]
3032

3133
[project.urls]

exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from collections.abc import Sequence
1818
from typing import Any, Mapping, Optional, List, Callable, TypeVar, Dict
1919

20+
import backoff
21+
2022
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
2123
from opentelemetry.proto.common.v1.common_pb2 import (
2224
InstrumentationScope as PB2InstrumentationScope,
@@ -130,3 +132,16 @@ def _get_resource_data(
130132
)
131133
)
132134
return resource_data
135+
136+
137+
# Work around API change between backoff 1.x and 2.x. Since 2.0.0 the backoff
138+
# wait generator API requires a first .send(None) before reading the backoff
139+
# values from the generator.
140+
_is_backoff_v2 = next(backoff.expo()) is None
141+
142+
143+
def _create_exp_backoff_generator(*args, **kwargs):
144+
gen = backoff.expo(*args, **kwargs)
145+
if _is_backoff_v2:
146+
gen.send(None)
147+
return gen

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

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838

3939
from opentelemetry.exporter.otlp.proto.common._internal import (
4040
_get_resource_data,
41+
_create_exp_backoff_generator,
4142
)
42-
import backoff
4343
from google.rpc.error_details_pb2 import RetryInfo
4444
from grpc import (
4545
ChannelCredentials,
@@ -137,19 +137,6 @@ def _get_credentials(creds, environ_key):
137137
return ssl_channel_credentials()
138138

139139

140-
# Work around API change between backoff 1.x and 2.x. Since 2.0.0 the backoff
141-
# wait generator API requires a first .send(None) before reading the backoff
142-
# values from the generator.
143-
_is_backoff_v2 = next(backoff.expo()) is None
144-
145-
146-
def _expo(*args, **kwargs):
147-
gen = backoff.expo(*args, **kwargs)
148-
if _is_backoff_v2:
149-
gen.send(None)
150-
return gen
151-
152-
153140
# pylint: disable=no-member
154141
class OTLPExporterMixin(
155142
ABC, Generic[SDKDataT, ExportServiceRequestT, ExportResultT]
@@ -266,7 +253,7 @@ def _export(
266253
# expo returns a generator that yields delay values which grow
267254
# exponentially. Once delay is greater than max_value, the yielded
268255
# value will remain constant.
269-
for delay in _expo(max_value=max_value):
256+
for delay in _create_exp_backoff_generator(max_value=max_value):
270257
if delay == max_value or self._shutdown:
271258
return self._result.FAILURE
272259

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,9 @@ def test_otlp_headers_from_env(self):
292292
(("user-agent", "OTel-OTLP-Exporter-Python/" + __version__),),
293293
)
294294

295-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
295+
@patch(
296+
"opentelemetry.exporter.otlp.proto.grpc.exporter._create_exp_backoff_generator"
297+
)
296298
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
297299
def test_unavailable(self, mock_sleep, mock_expo):
298300

@@ -306,7 +308,9 @@ def test_unavailable(self, mock_sleep, mock_expo):
306308
)
307309
mock_sleep.assert_called_with(1)
308310

309-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
311+
@patch(
312+
"opentelemetry.exporter.otlp.proto.grpc.exporter._create_exp_backoff_generator"
313+
)
310314
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
311315
def test_unavailable_delay(self, mock_sleep, mock_expo):
312316

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ def test_environ_to_compression(self):
6060
with self.assertRaises(InvalidCompressionValueException):
6161
environ_to_compression("test_invalid")
6262

63-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
63+
@patch(
64+
"opentelemetry.exporter.otlp.proto.grpc.exporter._create_exp_backoff_generator"
65+
)
6466
def test_export_warning(self, mock_expo):
6567
mock_expo.configure_mock(**{"return_value": [0]})
6668

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,9 @@ def test_otlp_exporter_endpoint(self, mock_secure, mock_insecure):
369369
mock_method.reset_mock()
370370

371371
# pylint: disable=no-self-use
372-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
372+
@patch(
373+
"opentelemetry.exporter.otlp.proto.grpc.exporter._create_exp_backoff_generator"
374+
)
373375
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.insecure_channel")
374376
@patch.dict("os.environ", {OTEL_EXPORTER_OTLP_COMPRESSION: "gzip"})
375377
def test_otlp_exporter_otlp_compression_envvar(
@@ -405,7 +407,9 @@ def test_otlp_exporter_otlp_compression_unspecified(
405407
"localhost:4317", compression=Compression.NoCompression
406408
)
407409

408-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
410+
@patch(
411+
"opentelemetry.exporter.otlp.proto.grpc.exporter._create_exp_backoff_generator"
412+
)
409413
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
410414
def test_unavailable(self, mock_sleep, mock_expo):
411415

@@ -420,7 +424,9 @@ def test_unavailable(self, mock_sleep, mock_expo):
420424
)
421425
mock_sleep.assert_called_with(1)
422426

423-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
427+
@patch(
428+
"opentelemetry.exporter.otlp.proto.grpc.exporter._create_exp_backoff_generator"
429+
)
424430
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
425431
def test_unavailable_delay(self, mock_sleep, mock_expo):
426432

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
from opentelemetry.attributes import BoundedAttributes
2929
from opentelemetry.exporter.otlp.proto.common._internal import (
3030
_encode_key_value,
31+
_is_backoff_v2,
3132
)
32-
from opentelemetry.exporter.otlp.proto.grpc.exporter import _is_backoff_v2
3333
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
3434
OTLPSpanExporter,
3535
)
@@ -460,7 +460,7 @@ def test_otlp_headers(self, mock_ssl_channel, mock_secure):
460460
(("user-agent", "OTel-OTLP-Exporter-Python/" + __version__),),
461461
)
462462

463-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.backoff")
463+
@patch("opentelemetry.exporter.otlp.proto.common._internal.backoff")
464464
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
465465
def test_handles_backoff_v2_api(self, mock_sleep, mock_backoff):
466466
# In backoff ~= 2.0.0 the first value yielded from expo is None.
@@ -477,7 +477,9 @@ def generate_delays(*args, **kwargs):
477477
self.exporter.export([self.span])
478478
mock_sleep.assert_called_once_with(1)
479479

480-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
480+
@patch(
481+
"opentelemetry.exporter.otlp.proto.grpc.exporter._create_exp_backoff_generator"
482+
)
481483
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
482484
def test_unavailable(self, mock_sleep, mock_expo):
483485

@@ -486,12 +488,13 @@ def test_unavailable(self, mock_sleep, mock_expo):
486488
add_TraceServiceServicer_to_server(
487489
TraceServiceServicerUNAVAILABLE(), self.server
488490
)
489-
self.assertEqual(
490-
self.exporter.export([self.span]), SpanExportResult.FAILURE
491-
)
491+
result = self.exporter.export([self.span])
492+
self.assertEqual(result, SpanExportResult.FAILURE)
492493
mock_sleep.assert_called_with(1)
493494

494-
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter._expo")
495+
@patch(
496+
"opentelemetry.exporter.otlp.proto.grpc.exporter._create_exp_backoff_generator"
497+
)
495498
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.sleep")
496499
def test_unavailable_delay(self, mock_sleep, mock_expo):
497500

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

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

23-
import backoff
2423
import requests
2524

25+
from opentelemetry.exporter.otlp.proto.common._internal import (
26+
_create_exp_backoff_generator,
27+
)
2628
from opentelemetry.exporter.otlp.proto.common._log_encoder import encode_logs
2729
from opentelemetry.sdk.environment_variables import (
2830
OTEL_EXPORTER_OTLP_CERTIFICATE,
@@ -56,18 +58,6 @@
5658
DEFAULT_LOGS_EXPORT_PATH = "v1/logs"
5759
DEFAULT_TIMEOUT = 10 # in seconds
5860

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

7262
class OTLPLogExporter(LogExporter):
7363

@@ -147,7 +137,9 @@ def export(self, batch: Sequence[LogData]) -> LogExportResult:
147137

148138
serialized_data = encode_logs(batch).SerializeToString()
149139

150-
for delay in _expo(max_value=self._MAX_RETRY_TIMEOUT):
140+
for delay in _create_exp_backoff_generator(
141+
max_value=self._MAX_RETRY_TIMEOUT
142+
):
151143

152144
if delay == self._MAX_RETRY_TIMEOUT:
153145
return LogExportResult.FAILURE

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

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from opentelemetry.exporter.otlp.proto.common._internal import (
2626
_get_resource_data,
27+
_create_exp_backoff_generator,
2728
)
2829
from opentelemetry.exporter.otlp.proto.common._internal.metrics_encoder import (
2930
OTLPMetricExporterMixin,
@@ -73,7 +74,6 @@
7374
from opentelemetry.sdk.resources import Resource as SDKResource
7475
from opentelemetry.util.re import parse_env_headers
7576

76-
import backoff
7777
import requests
7878
from opentelemetry.proto.resource.v1.resource_pb2 import (
7979
Resource as PB2Resource,
@@ -87,18 +87,6 @@
8787
DEFAULT_METRICS_EXPORT_PATH = "v1/metrics"
8888
DEFAULT_TIMEOUT = 10 # in seconds
8989

90-
# Work around API change between backoff 1.x and 2.x. Since 2.0.0 the backoff
91-
# wait generator API requires a first .send(None) before reading the backoff
92-
# values from the generator.
93-
_is_backoff_v2 = next(backoff.expo()) is None
94-
95-
96-
def _expo(*args, **kwargs):
97-
gen = backoff.expo(*args, **kwargs)
98-
if _is_backoff_v2:
99-
gen.send(None)
100-
return gen
101-
10290

10391
class OTLPMetricExporter(MetricExporter, OTLPMetricExporterMixin):
10492

@@ -181,7 +169,9 @@ def export(
181169
**kwargs,
182170
) -> MetricExportResult:
183171
serialized_data = encode_metrics(metrics_data)
184-
for delay in _expo(max_value=self._MAX_RETRY_TIMEOUT):
172+
for delay in _create_exp_backoff_generator(
173+
max_value=self._MAX_RETRY_TIMEOUT
174+
):
185175

186176
if delay == self._MAX_RETRY_TIMEOUT:
187177
return MetricExportResult.FAILURE

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

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

23-
import backoff
2423
import requests
2524

25+
from opentelemetry.exporter.otlp.proto.common._internal import (
26+
_create_exp_backoff_generator,
27+
)
2628
from opentelemetry.exporter.otlp.proto.common.trace_encoder import (
2729
encode_spans,
2830
)
@@ -54,18 +56,6 @@
5456
DEFAULT_TRACES_EXPORT_PATH = "v1/traces"
5557
DEFAULT_TIMEOUT = 10 # in seconds
5658

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-
6959

7060
class OTLPSpanExporter(SpanExporter):
7161

@@ -145,7 +135,9 @@ def export(self, spans) -> SpanExportResult:
145135

146136
serialized_data = encode_spans(spans).SerializeToString()
147137

148-
for delay in _expo(max_value=self._MAX_RETRY_TIMEOUT):
138+
for delay in _create_exp_backoff_generator(
139+
max_value=self._MAX_RETRY_TIMEOUT
140+
):
149141

150142
if delay == self._MAX_RETRY_TIMEOUT:
151143
return SpanExportResult.FAILURE

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from requests.models import Response
2222
from responses import POST, activate, add
2323

24+
from opentelemetry.exporter.otlp.proto.common._internal import _is_backoff_v2
2425
from opentelemetry.exporter.otlp.proto.common.metrics_encoder import (
2526
encode_metrics,
2627
)
@@ -31,7 +32,6 @@
3132
DEFAULT_METRICS_EXPORT_PATH,
3233
DEFAULT_TIMEOUT,
3334
OTLPMetricExporter,
34-
_is_backoff_v2,
3535
)
3636
from opentelemetry.sdk.environment_variables import (
3737
OTEL_EXPORTER_OTLP_CERTIFICATE,
@@ -298,7 +298,7 @@ def test_serialization(self, mock_post):
298298
)
299299

300300
@activate
301-
@patch("opentelemetry.exporter.otlp.proto.http.metric_exporter.backoff")
301+
@patch("opentelemetry.exporter.otlp.proto.common._internal.backoff")
302302
@patch("opentelemetry.exporter.otlp.proto.http.metric_exporter.sleep")
303303
def test_handles_backoff_v2_api(self, mock_sleep, mock_backoff):
304304
# In backoff ~= 2.0.0 the first value yielded from expo is None.

exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@
2222
import responses
2323

2424
from opentelemetry._logs import SeverityNumber
25+
from opentelemetry.exporter.otlp.proto.common._internal import _is_backoff_v2
2526
from opentelemetry.exporter.otlp.proto.http import Compression
2627
from opentelemetry.exporter.otlp.proto.http._log_exporter import (
2728
DEFAULT_COMPRESSION,
2829
DEFAULT_ENDPOINT,
2930
DEFAULT_LOGS_EXPORT_PATH,
3031
DEFAULT_TIMEOUT,
3132
OTLPLogExporter,
32-
_is_backoff_v2,
3333
)
3434
from opentelemetry.exporter.otlp.proto.http.version import __version__
3535
from opentelemetry.sdk._logs import LogData
@@ -168,7 +168,7 @@ def test_exporter_env(self):
168168
self.assertIsInstance(exporter._session, requests.Session)
169169

170170
@responses.activate
171-
@patch("opentelemetry.exporter.otlp.proto.http._log_exporter.backoff")
171+
@patch("opentelemetry.exporter.otlp.proto.common._internal.backoff")
172172
@patch("opentelemetry.exporter.otlp.proto.http._log_exporter.sleep")
173173
def test_handles_backoff_v2_api(self, mock_sleep, mock_backoff):
174174
# In backoff ~= 2.0.0 the first value yielded from expo is None.

exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_span_exporter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
import requests
2020
import responses
2121

22+
from opentelemetry.exporter.otlp.proto.common._internal import _is_backoff_v2
2223
from opentelemetry.exporter.otlp.proto.http import Compression
2324
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
2425
DEFAULT_COMPRESSION,
2526
DEFAULT_ENDPOINT,
2627
DEFAULT_TIMEOUT,
2728
DEFAULT_TRACES_EXPORT_PATH,
2829
OTLPSpanExporter,
29-
_is_backoff_v2,
3030
)
3131
from opentelemetry.exporter.otlp.proto.http.version import __version__
3232
from opentelemetry.sdk.environment_variables import (
@@ -204,7 +204,7 @@ def test_headers_parse_from_env(self):
204204

205205
# pylint: disable=no-self-use
206206
@responses.activate
207-
@patch("opentelemetry.exporter.otlp.proto.http.trace_exporter.backoff")
207+
@patch("opentelemetry.exporter.otlp.proto.common._internal.backoff")
208208
@patch("opentelemetry.exporter.otlp.proto.http.trace_exporter.sleep")
209209
def test_handles_backoff_v2_api(self, mock_sleep, mock_backoff):
210210
# In backoff ~= 2.0.0 the first value yielded from expo is None.

0 commit comments

Comments
 (0)