From b7f2d167d58345a0d26fd2428f84b2da28cbc86a Mon Sep 17 00:00:00 2001 From: Patrick Yang Date: Tue, 27 Oct 2020 15:46:19 -0700 Subject: [PATCH] Fix BatchExportSpanProcessor not resetting timeout on worker loop (#1218) --- .../exporter/datadog/spanprocessor.py | 3 +- .../tests/test_datadog_exporter.py | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/spanprocessor.py b/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/spanprocessor.py index d94cf0f10..3a1188e0b 100644 --- a/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/spanprocessor.py +++ b/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/spanprocessor.py @@ -119,7 +119,8 @@ class DatadogExportSpanProcessor(SpanProcessor): with self.condition: self.condition.wait(timeout) if not self.check_traces_queue: - # spurious notification, let's wait again + # spurious notification, let's wait again, reset timeout + timeout = self.schedule_delay_millis / 1e3 continue if self.done: # missing spans will be sent when calling flush diff --git a/exporter/opentelemetry-exporter-datadog/tests/test_datadog_exporter.py b/exporter/opentelemetry-exporter-datadog/tests/test_datadog_exporter.py index bd8370c10..2a8b753e5 100644 --- a/exporter/opentelemetry-exporter-datadog/tests/test_datadog_exporter.py +++ b/exporter/opentelemetry-exporter-datadog/tests/test_datadog_exporter.py @@ -483,6 +483,42 @@ class TestDatadogSpanExporter(unittest.TestCase): tracer_provider.shutdown() + def test_batch_span_processor_reset_timeout(self): + """Test that the scheduled timeout is reset on cycles without spans""" + delay = 50 + # pylint: disable=protected-access + exporter = MockDatadogSpanExporter() + exporter._agent_writer.write.side_effect = lambda spans: time.sleep( + 0.05 + ) + span_processor = datadog.DatadogExportSpanProcessor( + exporter, schedule_delay_millis=delay + ) + tracer_provider = trace.TracerProvider() + tracer_provider.add_span_processor(span_processor) + tracer = tracer_provider.get_tracer(__name__) + with mock.patch.object(span_processor.condition, "wait") as mock_wait: + with tracer.start_span("foo"): + pass + + # give some time for exporter to loop + # since wait is mocked it should return immediately + time.sleep(0.1) + mock_wait_calls = list(mock_wait.mock_calls) + + # find the index of the call that processed the singular span + for idx, wait_call in enumerate(mock_wait_calls): + _, args, __ = wait_call + if args[0] <= 0: + after_calls = mock_wait_calls[idx + 1 :] + break + + self.assertTrue( + all(args[0] >= 0.05 for _, args, __ in after_calls) + ) + + span_processor.shutdown() + def test_span_processor_accepts_parent_context(self): span_processor = mock.Mock( wraps=datadog.DatadogExportSpanProcessor(self.exporter)