From 3b2725880d7f4b884796c7158619e182237abcfe Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Tue, 28 Oct 2025 09:43:12 +0100 Subject: [PATCH 1/4] prevent deadlock --- CHANGELOG.md | 4 ++++ .../src/opentelemetry/exporter/richconsole/__init__.py | 7 ++++--- .../tests/test_rich_exporter.py | 10 ++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 024990c91d..37ff9d72cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- `opentelemetry-instrumentation-richconsole`: Prevent deadlock when parent span is not part of the batch + ## Version 1.38.0/0.59b0 (2025-10-16) ### Fixed diff --git a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py index 59415e2bd8..7f29ce8c1c 100644 --- a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py +++ b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py @@ -173,14 +173,15 @@ def spans_to_tree(spans: typing.Sequence[ReadableSpan]) -> Dict[str, Tree]: trees = {} parents = {} spans = list(spans) + span_ids = {s.context.span_id for s in spans} while spans: for span in spans: - if not span.parent: + if not span.parent or span.parent.span_id not in span_ids: trace_id = opentelemetry.trace.format_trace_id( span.context.trace_id ) - trees[trace_id] = Tree(label=f"Trace {trace_id}") - child = trees[trace_id].add( + tree = trees.setdefault(trace_id, Tree(label=f"Trace {trace_id}")) + child = tree.add( label=Text.from_markup( f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}" ) diff --git a/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py b/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py index f4dcd49fe9..b02fc2a7db 100644 --- a/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py +++ b/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py @@ -96,3 +96,13 @@ def test_multiple_traces(tracer_provider): parent_2.name in child.label for child in trees[traceid_1].children[0].children ) + +def test_no_deadlock(tracer_provider): + # non-regression test for https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3254 + + tracer = tracer_provider.get_tracer(__name__) + with tracer.start_as_current_span("parent"): + with tracer.start_as_current_span("child") as child: + pass + + RichConsoleSpanExporter.spans_to_tree((child,)) From 7371c17ef9b96eb34149a924ec97efcf4fc321d5 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Thu, 30 Oct 2025 08:56:18 +0100 Subject: [PATCH 2/4] fix changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ff9d72cf..2db953a3d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- `opentelemetry-instrumentation-richconsole`: Prevent deadlock when parent span is not part of the batch +- `opentelemetry-exporter-richconsole`: Prevent deadlock when parent span is not part of the batch + ([#3900](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3900)) ## Version 1.38.0/0.59b0 (2025-10-16) From 0d5e5d5e64270511e819ec4dc1b97b223064d93e Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Thu, 30 Oct 2025 09:00:08 +0100 Subject: [PATCH 3/4] lint --- .../src/opentelemetry/exporter/richconsole/__init__.py | 4 +++- .../tests/test_rich_exporter.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py index 7f29ce8c1c..3e3aab7c74 100644 --- a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py +++ b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py @@ -180,7 +180,9 @@ def spans_to_tree(spans: typing.Sequence[ReadableSpan]) -> Dict[str, Tree]: trace_id = opentelemetry.trace.format_trace_id( span.context.trace_id ) - tree = trees.setdefault(trace_id, Tree(label=f"Trace {trace_id}")) + tree = trees.setdefault( + trace_id, Tree(label=f"Trace {trace_id}") + ) child = tree.add( label=Text.from_markup( f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}" diff --git a/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py b/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py index b02fc2a7db..b7e9a37bdf 100644 --- a/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py +++ b/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py @@ -97,6 +97,7 @@ def test_multiple_traces(tracer_provider): for child in trees[traceid_1].children[0].children ) + def test_no_deadlock(tracer_provider): # non-regression test for https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3254 From e7a2937bb92564e0691b7ad1c3536bdbd98005b7 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Thu, 30 Oct 2025 22:23:39 +0100 Subject: [PATCH 4/4] add test timeout --- .../opentelemetry-exporter-richconsole/test-requirements.txt | 1 + .../tests/test_rich_exporter.py | 1 + 2 files changed, 2 insertions(+) diff --git a/exporter/opentelemetry-exporter-richconsole/test-requirements.txt b/exporter/opentelemetry-exporter-richconsole/test-requirements.txt index fb9ccfdb7e..40d37f1d66 100644 --- a/exporter/opentelemetry-exporter-richconsole/test-requirements.txt +++ b/exporter/opentelemetry-exporter-richconsole/test-requirements.txt @@ -9,6 +9,7 @@ pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.17.2 pytest==7.4.4 +pytest-timeout==2.3.1 rich==13.7.1 tomli==2.0.1 typing_extensions==4.12.2 diff --git a/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py b/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py index b7e9a37bdf..44bebb4839 100644 --- a/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py +++ b/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py @@ -98,6 +98,7 @@ def test_multiple_traces(tracer_provider): ) +@pytest.mark.timeout(30) def test_no_deadlock(tracer_provider): # non-regression test for https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3254