You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix record operation result conflict handling (#178)
Do not ignore conflicts. When conflicts are detected (i.e., another
process is attempting to record the outcome of the step after the first
recording happened), the logic will detect it and return an conflict
error.
This helps the end user detect when unexpected concurrent executions
take place.
This PR also fixes the logic we use to detect concurrent executions when
executing a workflow function. Specifically:
- `newStepExecutionError` now wraps the underlying error (it was losing
it).
- Implement a custom `errors.Is` function to compare `DBOSError`
instances based on their underlying code.
- Replace the `errors.As` check by an `error.Is`. `As` looks up for the
first instance of a matching error, whereas `Is` iterates through the
error tree until its condition is satisfied.
Note that the conflict detection is already exercised in the
`TestWorkflowRecovery`, which recovers a workflow blocked in a step.
Both original or recovery execution race to record the outcome of the
step, and the loser falls back to polling the database for the workflow
result.
returnnil, newStepExecutionError("", stepOpts.stepName, "workflow state not found in context: are you running this step within a workflow?")
1146
+
returnnil, newStepExecutionError("", stepOpts.stepName, fmt.Errorf("workflow state not found in context: are you running this step within a workflow?"))
1148
1147
}
1149
1148
1150
1149
// This should not happen when called from the package-level RunAsStep
1151
1150
iffn==nil {
1152
-
returnnil, newStepExecutionError(wfState.workflowID, stepOpts.stepName, "step function cannot be nil")
1151
+
returnnil, newStepExecutionError(wfState.workflowID, stepOpts.stepName, fmt.Errorf("step function cannot be nil"))
1153
1152
}
1154
1153
1155
1154
// If within a step, just run the function directly
assert.Nil(t, steps[0].Error, "workflow %d first step should not have error", i)
1426
1429
assert.Nil(t, steps[1].Error, "workflow %d second step should not have error", i)
1427
1430
}
1431
+
1432
+
// At least 5 of the 2nd steps should have errored due to execution race
1433
+
// Check they are DBOSErrors with StepExecutionError wrapping a ConflictingIDError
1434
+
require.GreaterOrEqual(t, len(secondStepErrors), 5, "expected at least 5 errors from second steps due to recovery race, got %d", len(secondStepErrors))
1435
+
for_, err:=rangesecondStepErrors {
1436
+
dbosErr, ok:=err.(*DBOSError)
1437
+
require.True(t, ok, "expected error to be of type *DBOSError, got %T", err)
1438
+
require.Equal(t, StepExecutionError, dbosErr.Code, "expected error code to be StepExecutionError, got %v", dbosErr.Code)
1439
+
require.True(t, errors.Is(dbosErr.Unwrap(), &DBOSError{Code: ConflictingIDError}), "expected underlying error to be ConflictingIDError, got %T", dbosErr.Unwrap())
0 commit comments