From e402b22f9f3651a12ef43991a4aadb2655320c75 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Tue, 28 Jul 2026 22:41:59 -0700 Subject: [PATCH] fix(otel): leave pending invocation spans unset --- .../execution_plugin.py | 12 ++++-------- .../invocation_plugin.py | 9 +++------ .../tests/test_execution_plugin.py | 5 ++--- .../tests/test_invocation_plugin.py | 2 +- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/execution_plugin.py b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/execution_plugin.py index 46a47109..068f6a5f 100644 --- a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/execution_plugin.py +++ b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/execution_plugin.py @@ -285,19 +285,15 @@ def on_invocation_end(self, info: InvocationEndInfo) -> None: # End the invocation span regardless of terminal status. Record the # invocation status and map it to a span status: - # SUCCEEDED/PENDING -> OK (this invocation did its work, whether it - # completed the execution or cleanly suspended) - # FAILED -> ERROR - # RETRY -> UNSET - # RETRY is left UNSET because the plugin interface cannot tell whether the - # execution/workflow was STOPPED or TIMED_OUT: a RETRY invocation is not a - # definitive failure of the execution, so we avoid marking the span ERROR. + # SUCCEEDED -> OK + # FAILED -> ERROR + # PENDING/RETRY -> UNSET if self._invocation_span is not None: self._invocation_span.set_attribute( "durable.invocation.status", info.status.value if info.status else "", ) - if info.status in (InvocationStatus.SUCCEEDED, InvocationStatus.PENDING): + if info.status is InvocationStatus.SUCCEEDED: self._invocation_span.set_status(StatusCode.OK) elif info.status is InvocationStatus.FAILED: self._invocation_span.set_status( diff --git a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/invocation_plugin.py b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/invocation_plugin.py index d335b2b5..815378f4 100644 --- a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/invocation_plugin.py +++ b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/invocation_plugin.py @@ -350,12 +350,9 @@ def on_invocation_end(self, info: InvocationEndInfo) -> None: invocation_span.set_attribute( "durable.invocation.status", info.status.value ) - # Span status mapping: SUCCEEDED/PENDING -> OK, FAILED -> ERROR, - # RETRY -> UNSET. RETRY is left UNSET because the plugin interface - # cannot tell whether the execution/workflow was STOPPED or - # TIMED_OUT, so a RETRY invocation is not treated as a definitive - # failure of the execution. - if info.status in (InvocationStatus.SUCCEEDED, InvocationStatus.PENDING): + # Span status mapping: SUCCEEDED -> OK, FAILED -> ERROR, and + # non-terminal PENDING/RETRY -> UNSET. + if info.status is InvocationStatus.SUCCEEDED: invocation_span.set_status(StatusCode.OK) elif info.status is InvocationStatus.FAILED: invocation_span.set_status( diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin.py index 1dd00570..6ac656d1 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin.py @@ -322,15 +322,14 @@ def test_open_operation_span_not_exported_at_invocation_end(): @pytest.mark.parametrize( ("status", "expected_code"), [ - (InvocationStatus.PENDING, trace.StatusCode.OK), + (InvocationStatus.PENDING, trace.StatusCode.UNSET), (InvocationStatus.SUCCEEDED, trace.StatusCode.OK), (InvocationStatus.RETRY, trace.StatusCode.UNSET), (InvocationStatus.FAILED, trace.StatusCode.ERROR), ], ) def test_invocation_span_status_kind_and_attributes(status, expected_code): - """Invocation span is INTERNAL, carries status/first; SUCCEEDED/PENDING are - OK, FAILED is ERROR, and RETRY is UNSET (STOPPED/TIMED_OUT indistinguishable).""" + """Invocation span is INTERNAL and only terminal outcomes set span status.""" plugin, exporter = _create_plugin() plugin.on_invocation_start(_invocation_start_info()) plugin.on_invocation_end(_invocation_end_info(status=status)) diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin.py index 116fa556..580ab6b3 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin.py @@ -175,7 +175,7 @@ def test_invocation_span_records_subsequent_invocation(): @pytest.mark.parametrize( ("invocation_status", "expected_span_status"), [ - (InvocationStatus.PENDING, StatusCode.OK), + (InvocationStatus.PENDING, StatusCode.UNSET), (InvocationStatus.RETRY, StatusCode.UNSET), (InvocationStatus.SUCCEEDED, StatusCode.OK), (InvocationStatus.FAILED, StatusCode.ERROR),