@@ -1212,6 +1212,107 @@ def orchestrator(ctx: task.OrchestrationContext, _):
12121212 assert str (ex ) in complete_action .failureDetails .errorMessage
12131213
12141214
1215+ def test_when_all_failure_after_success_bubbles_to_orchestrator ():
1216+ """Tests that when_all correctly surfaces a failure to the orchestrator
1217+ even when succeeding tasks complete before the failing one.
1218+
1219+ This is a regression test: previously the exception from the failed task
1220+ was swallowed when another task had already completed successfully.
1221+ """
1222+
1223+ def dummy_activity (ctx , _ ):
1224+ pass
1225+
1226+ def orchestrator (ctx : task .OrchestrationContext , _ ):
1227+ t1 = ctx .call_activity (dummy_activity , input = 'will-succeed' )
1228+ t2 = ctx .call_activity (dummy_activity , input = 'will-fail' )
1229+ try :
1230+ yield task .when_all ([t1 , t2 ])
1231+ except task .TaskFailedError :
1232+ return 'caught'
1233+ return 'not caught'
1234+
1235+ registry = worker ._Registry ()
1236+ orchestrator_name = registry .add_orchestrator (orchestrator )
1237+ activity_name = registry .add_activity (dummy_activity )
1238+
1239+ old_events = [
1240+ helpers .new_workflow_started_event (),
1241+ helpers .new_execution_started_event (
1242+ orchestrator_name , TEST_INSTANCE_ID , encoded_input = None
1243+ ),
1244+ helpers .new_task_scheduled_event (1 , activity_name ),
1245+ helpers .new_task_scheduled_event (2 , activity_name ),
1246+ ]
1247+
1248+ # t1 succeeds FIRST, then t2 fails — this is the order that triggered the bug
1249+ ex = Exception ('activity error' )
1250+ new_events = [
1251+ helpers .new_task_completed_event (1 , encoded_output = json .dumps ('ok' )),
1252+ helpers .new_task_failed_event (2 , ex ),
1253+ ]
1254+
1255+ executor = worker ._OrchestrationExecutor (registry , TEST_LOGGER )
1256+ result = executor .execute (TEST_INSTANCE_ID , old_events , new_events )
1257+ actions = result .actions
1258+
1259+ complete_action = get_and_validate_single_complete_workflow_action (actions )
1260+ # The orchestrator should have caught the exception and returned 'caught'
1261+ assert complete_action .workflowStatus == pb .ORCHESTRATION_STATUS_COMPLETED
1262+ assert complete_action .result .value == json .dumps ('caught' )
1263+
1264+
1265+ def test_when_all_success_after_failure_does_not_crash ():
1266+ """Tests that task completions arriving after when_all already failed
1267+ do not crash the orchestration.
1268+
1269+ This is a regression test: previously a ValueError was raised when
1270+ a successful task completed after the WhenAllTask was already marked
1271+ complete due to a prior child failure.
1272+ """
1273+
1274+ def dummy_activity (ctx , _ ):
1275+ pass
1276+
1277+ def orchestrator (ctx : task .OrchestrationContext , _ ):
1278+ t1 = ctx .call_activity (dummy_activity , input = 'will-fail' )
1279+ t2 = ctx .call_activity (dummy_activity , input = 'will-succeed' )
1280+ try :
1281+ yield task .when_all ([t1 , t2 ])
1282+ except task .TaskFailedError :
1283+ return 'caught'
1284+ return 'not caught'
1285+
1286+ registry = worker ._Registry ()
1287+ orchestrator_name = registry .add_orchestrator (orchestrator )
1288+ activity_name = registry .add_activity (dummy_activity )
1289+
1290+ old_events = [
1291+ helpers .new_workflow_started_event (),
1292+ helpers .new_execution_started_event (
1293+ orchestrator_name , TEST_INSTANCE_ID , encoded_input = None
1294+ ),
1295+ helpers .new_task_scheduled_event (1 , activity_name ),
1296+ helpers .new_task_scheduled_event (2 , activity_name ),
1297+ ]
1298+
1299+ # t1 fails FIRST, then t2 succeeds — this would previously raise ValueError
1300+ ex = Exception ('activity error' )
1301+ new_events = [
1302+ helpers .new_task_failed_event (1 , ex ),
1303+ helpers .new_task_completed_event (2 , encoded_output = json .dumps ('ok' )),
1304+ ]
1305+
1306+ executor = worker ._OrchestrationExecutor (registry , TEST_LOGGER )
1307+ result = executor .execute (TEST_INSTANCE_ID , old_events , new_events )
1308+ actions = result .actions
1309+
1310+ complete_action = get_and_validate_single_complete_workflow_action (actions )
1311+ # The orchestrator should have caught the exception and returned 'caught'
1312+ assert complete_action .workflowStatus == pb .ORCHESTRATION_STATUS_COMPLETED
1313+ assert complete_action .result .value == json .dumps ('caught' )
1314+
1315+
12151316def test_when_any ():
12161317 """Tests that a when_any pattern works correctly"""
12171318
0 commit comments