@@ -594,11 +594,14 @@ def start_workflow(
594594 ctx = new_wf_ctx
595595 new_child_workflow_id = ctx .id_assigned_for_next_workflow
596596 if ctx .has_parent ():
597- child_workflow_id = dbos ._sys_db .check_child_workflow (
598- ctx .parent_workflow_id , ctx .parent_workflow_fid
597+ recorded_result = dbos ._sys_db .check_operation_execution (
598+ ctx .parent_workflow_id , ctx .parent_workflow_fid , get_dbos_func_name ( func )
599599 )
600- if child_workflow_id is not None :
601- return WorkflowHandlePolling (child_workflow_id , dbos )
600+ if recorded_result and recorded_result ["error" ]:
601+ e : Exception = dbos ._sys_db .serializer .deserialize (recorded_result ["error" ])
602+ raise e
603+ elif recorded_result and recorded_result ["child_workflow_id" ]:
604+ return WorkflowHandlePolling (recorded_result ["child_workflow_id" ], dbos )
602605
603606 status = _init_workflow (
604607 dbos ,
@@ -690,13 +693,19 @@ async def start_workflow_async(
690693 ctx = new_wf_ctx
691694 new_child_workflow_id = ctx .id_assigned_for_next_workflow
692695 if ctx .has_parent ():
693- child_workflow_id = await asyncio .to_thread (
694- dbos ._sys_db .check_child_workflow ,
696+ recorded_result = await asyncio .to_thread (
697+ dbos ._sys_db .check_operation_execution ,
695698 ctx .parent_workflow_id ,
696699 ctx .parent_workflow_fid ,
700+ get_dbos_func_name (func ),
697701 )
698- if child_workflow_id is not None :
699- return WorkflowHandleAsyncPolling (child_workflow_id , dbos )
702+ if recorded_result and recorded_result ["error" ]:
703+ e : Exception = dbos ._sys_db .serializer .deserialize (recorded_result ["error" ])
704+ raise e
705+ elif recorded_result and recorded_result ["child_workflow_id" ]:
706+ return WorkflowHandleAsyncPolling (
707+ recorded_result ["child_workflow_id" ], dbos
708+ )
700709
701710 status = await asyncio .to_thread (
702711 _init_workflow ,
@@ -815,11 +824,16 @@ def recorded_result_inner(func: Callable[[], R]) -> R:
815824 workflow_id = ctx .workflow_id
816825
817826 if ctx .has_parent ():
818- child_workflow_id = dbos ._sys_db .check_child_workflow (
819- ctx .parent_workflow_id , ctx .parent_workflow_fid
827+ r = dbos ._sys_db .check_operation_execution (
828+ ctx .parent_workflow_id ,
829+ ctx .parent_workflow_fid ,
830+ get_dbos_func_name (func ),
820831 )
821- if child_workflow_id is not None :
822- return recorded_result (child_workflow_id , dbos )
832+ if r and r ["error" ]:
833+ e : Exception = dbos ._sys_db .serializer .deserialize (r ["error" ])
834+ raise e
835+ elif r and r ["child_workflow_id" ]:
836+ return recorded_result (r ["child_workflow_id" ], dbos )
823837
824838 status = _init_workflow (
825839 dbos ,
@@ -906,12 +920,6 @@ def invoke_tx(*args: Any, **kwargs: Any) -> Any:
906920 )
907921
908922 dbos = dbosreg .dbos
909- ctx = assert_current_dbos_context ()
910- status = dbos ._sys_db .get_workflow_status (ctx .workflow_id )
911- if status and status ["status" ] == WorkflowStatusString .CANCELLED .value :
912- raise DBOSWorkflowCancelledError (
913- f"Workflow { ctx .workflow_id } is cancelled. Aborting transaction { transaction_name } ."
914- )
915923 assert (
916924 dbos ._app_db
917925 ), "Transactions can only be used if DBOS is configured with an application_database_url"
@@ -922,6 +930,26 @@ def invoke_tx(*args: Any, **kwargs: Any) -> Any:
922930 }
923931 with EnterDBOSTransaction (session , attributes = attributes ):
924932 ctx = assert_current_dbos_context ()
933+ # Check if the step record for this transaction exists
934+ recorded_step_output = dbos ._sys_db .check_operation_execution (
935+ ctx .workflow_id , ctx .function_id , transaction_name
936+ )
937+ if recorded_step_output :
938+ dbos .logger .debug (
939+ f"Replaying transaction, id: { ctx .function_id } , name: { attributes ['name' ]} "
940+ )
941+ if recorded_step_output ["error" ]:
942+ step_error : Exception = dbos ._serializer .deserialize (
943+ recorded_step_output ["error" ]
944+ )
945+ raise step_error
946+ elif recorded_step_output ["output" ]:
947+ return dbos ._serializer .deserialize (
948+ recorded_step_output ["output" ]
949+ )
950+ else :
951+ raise Exception ("Output and error are both None" )
952+
925953 txn_output : TransactionResultInternal = {
926954 "workflow_uuid" : ctx .workflow_id ,
927955 "function_id" : ctx .function_id ,
@@ -932,6 +960,14 @@ def invoke_tx(*args: Any, **kwargs: Any) -> Any:
932960 "txn_id" : None ,
933961 "function_name" : transaction_name ,
934962 }
963+ step_output : OperationResultInternal = {
964+ "workflow_uuid" : ctx .workflow_id ,
965+ "function_id" : ctx .function_id ,
966+ "function_name" : transaction_name ,
967+ "output" : None ,
968+ "error" : None ,
969+ "started_at_epoch_ms" : int (time .time () * 1000 ),
970+ }
935971 retry_wait_seconds = 0.001
936972 backoff_factor = 1.5
937973 max_retry_wait_seconds = 2.0
@@ -970,8 +1006,18 @@ def invoke_tx(*args: Any, **kwargs: Any) -> Any:
9701006 )
9711007 )
9721008 has_recorded_error = True
1009+ step_output ["error" ] = recorded_output ["error" ]
1010+ dbos ._sys_db .record_operation_result (
1011+ step_output
1012+ )
9731013 raise deserialized_error
9741014 elif recorded_output ["output" ]:
1015+ step_output ["output" ] = recorded_output [
1016+ "output"
1017+ ]
1018+ dbos ._sys_db .record_operation_result (
1019+ step_output
1020+ )
9751021 return dbos ._serializer .deserialize (
9761022 recorded_output ["output" ]
9771023 )
@@ -1028,10 +1074,13 @@ def invoke_tx(*args: Any, **kwargs: Any) -> Any:
10281074 finally :
10291075 # Don't record the error if it was already recorded
10301076 if txn_error and not has_recorded_error :
1031- txn_output ["error" ] = dbos . _serializer . serialize (
1032- txn_error
1077+ step_output [ "error" ] = txn_output ["error" ] = (
1078+ dbos . _serializer . serialize ( txn_error )
10331079 )
10341080 dbos ._app_db .record_transaction_error (txn_output )
1081+ dbos ._sys_db .record_operation_result (step_output )
1082+ step_output ["output" ] = dbos ._serializer .serialize (output )
1083+ dbos ._sys_db .record_operation_result (step_output )
10351084 return output
10361085
10371086 if inspect .iscoroutinefunction (func ):
0 commit comments