@@ -42,6 +42,7 @@ type SystemDatabase interface {
4242 Recv (ctx context.Context , input WorkflowRecvInput ) (any , error )
4343 SetEvent (ctx context.Context , input WorkflowSetEventInput ) error
4444 GetEvent (ctx context.Context , input WorkflowGetEventInput ) (any , error )
45+ Sleep (ctx context.Context , duration time.Duration ) (time.Duration , error )
4546}
4647
4748type systemDatabase struct {
@@ -712,10 +713,12 @@ func (s *systemDatabase) RecordOperationResult(ctx context.Context, input record
712713 getLogger().Debug("RecordOperationResult SQL", "sql", commandTag.String())
713714 */
714715
715- // TODO return DBOSWorkflowConflictIDError(result["workflow_uuid"]) on 23505 conflict ID error
716716 if err != nil {
717717 getLogger ().Error ("RecordOperationResult Error occurred" , "error" , err )
718- return fmt .Errorf ("failed to record operation result: %w" , err )
718+ if pgErr , ok := err .(* pgconn.PgError ); ok && pgErr .Code == "23505" {
719+ return newWorkflowConflictIDError (input .workflowID )
720+ }
721+ return err
719722 }
720723
721724 if commandTag .RowsAffected () == 0 {
@@ -980,6 +983,86 @@ func (s *systemDatabase) GetWorkflowSteps(ctx context.Context, workflowID string
980983 return steps , nil
981984}
982985
986+ // Sleep is a special type of step that sleeps for a specified duration
987+ // A wakeup time is computed and recorded in the database
988+ // If we sleep is re-executed, it will only sleep for the remaining duration until the wakeup time
989+ func (s * systemDatabase ) Sleep (ctx context.Context , duration time.Duration ) (time.Duration , error ) {
990+ functionName := "DBOS.sleep"
991+
992+ // Get workflow state from context
993+ wfState , ok := ctx .Value (workflowStateKey ).(* workflowState )
994+ if ! ok || wfState == nil {
995+ return 0 , newStepExecutionError ("" , functionName , "workflow state not found in context: are you running this step within a workflow?" )
996+ }
997+
998+ if wfState .isWithinStep {
999+ return 0 , newStepExecutionError (wfState .workflowID , functionName , "cannot call Sleep within a step" )
1000+ }
1001+
1002+ stepID := wfState .NextStepID ()
1003+
1004+ // Check if operation was already executed
1005+ checkInput := checkOperationExecutionDBInput {
1006+ workflowID : wfState .workflowID ,
1007+ stepID : stepID ,
1008+ stepName : functionName ,
1009+ }
1010+ recordedResult , err := s .CheckOperationExecution (ctx , checkInput )
1011+ if err != nil {
1012+ return 0 , fmt .Errorf ("failed to check operation execution: %w" , err )
1013+ }
1014+
1015+ var endTime time.Time
1016+
1017+ if recordedResult != nil {
1018+ if recordedResult .output == nil { // This should never happen
1019+ return 0 , fmt .Errorf ("no recorded end time for recorded sleep operation" )
1020+ }
1021+
1022+ // The output should be a time.Time representing the end time
1023+ endTimeInterface , ok := recordedResult .output .(time.Time )
1024+ if ! ok {
1025+ return 0 , fmt .Errorf ("recorded output is not a time.Time: %T" , recordedResult .output )
1026+ }
1027+ endTime = endTimeInterface
1028+
1029+ if recordedResult .err != nil { // This should never happen
1030+ return 0 , recordedResult .err
1031+ }
1032+ } else {
1033+ // First execution: calculate and record the end time
1034+ getLogger ().Debug ("Durable sleep" , "stepID" , stepID , "duration" , duration )
1035+
1036+ endTime = time .Now ().Add (duration )
1037+
1038+ // Record the operation result with the calculated end time
1039+ recordInput := recordOperationResultDBInput {
1040+ workflowID : wfState .workflowID ,
1041+ stepID : stepID ,
1042+ stepName : functionName ,
1043+ output : endTime ,
1044+ err : nil ,
1045+ }
1046+
1047+ err = s .RecordOperationResult (ctx , recordInput )
1048+ if err != nil {
1049+ // Check if this is a ConflictingWorkflowError (operation already recorded by another process)
1050+ if dbosErr , ok := err .(* DBOSError ); ok && dbosErr .Code == ConflictingIDError {
1051+ } else {
1052+ return 0 , fmt .Errorf ("failed to record sleep operation result: %w" , err )
1053+ }
1054+ }
1055+ }
1056+
1057+ // Calculate remaining duration until wake up time
1058+ remainingDuration := max (0 , time .Until (endTime ))
1059+
1060+ // Actually sleep for the remaining duration
1061+ time .Sleep (remainingDuration )
1062+
1063+ return remainingDuration , nil
1064+ }
1065+
9831066/****************************************/
9841067/******* WORKFLOW COMMUNICATIONS ********/
9851068/****************************************/
0 commit comments