Skip to content

Commit 242afd6

Browse files
committed
Harden failure property providers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 26daf565-a1f4-4bf0-9540-b57f31a4a12b
1 parent 61f1196 commit 242afd6

3 files changed

Lines changed: 35 additions & 4 deletions

File tree

‎durabletask/internal/helpers.py‎

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,33 @@ def new_failure_details(
223223
inner: BaseException | None = ex.__cause__ or ex.__context__
224224
if len(_visited) > 10 or (inner and id(inner) in _visited) or not isinstance(inner, Exception):
225225
inner = None
226-
properties = None
226+
properties: dict[str, struct_pb2.Value] | None = None
227227
if exception_properties_provider is not None:
228228
try:
229-
properties = exception_properties_provider.get_exception_properties(ex)
229+
provider_properties = exception_properties_provider.get_exception_properties(ex)
230230
except Exception:
231231
if logger is not None:
232232
logger.warning(
233233
"ExceptionPropertiesProvider failed while processing %s.",
234234
get_qualified_name(type(ex)),
235235
exc_info=True)
236+
else:
237+
try:
238+
if provider_properties is not None:
239+
if not isinstance(provider_properties, Mapping):
240+
raise TypeError(
241+
"ExceptionPropertiesProvider.get_exception_properties() "
242+
"must return a mapping or None.")
243+
properties = {
244+
str(key): protobuf_value_from_python(value)
245+
for key, value in cast(Mapping[Any, Any], provider_properties).items()
246+
}
247+
except Exception:
248+
if logger is not None:
249+
logger.warning(
250+
"ExceptionPropertiesProvider returned invalid properties for %s.",
251+
get_qualified_name(type(ex)),
252+
exc_info=True)
236253

237254
failure_details = pb.TaskFailureDetails(
238255
errorType=get_qualified_name(type(ex)),
@@ -244,8 +261,8 @@ def new_failure_details(
244261
if inner else None)
245262
)
246263
if properties:
247-
for key, value in cast(Mapping[Any, Any], properties).items():
248-
failure_details.properties[str(key)].CopyFrom(protobuf_value_from_python(value))
264+
for key, value in properties.items():
265+
failure_details.properties[key].CopyFrom(value)
249266
return failure_details
250267

251268

‎tests/durabletask/entities/test_entity_failure_handling.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def test_orchestrator(ctx: task.OrchestrationContext, _):
163163
except task.TaskFailedError as error:
164164
assert error.details.properties is not None
165165
return error.details.properties
166+
return None
166167

167168
with worker.TaskHubGrpcWorker(
168169
host_address=HOST,

‎tests/durabletask/test_failure_details.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,19 @@ def get_exception_properties(self, exception: Exception):
225225
assert "ExceptionPropertiesProvider failed" in caplog.text
226226

227227

228+
def test_invalid_failure_details_properties_do_not_mask_original(caplog: LogCaptureFixture):
229+
class InvalidProvider:
230+
def get_exception_properties(self, exception: Exception):
231+
return ["invalid"]
232+
233+
details = helpers.new_failure_details(
234+
ValueError("original"), InvalidProvider(), TEST_LOGGER)
235+
236+
assert details.errorMessage == "original"
237+
assert not details.properties
238+
assert "ExceptionPropertiesProvider returned invalid properties" in caplog.text
239+
240+
228241
def test_task_failure_and_orchestration_state_expose_properties():
229242
proto = helpers.new_failure_details(ValueError("boom"), _PropertiesProvider())
230243

0 commit comments

Comments
 (0)