Will Promise.anyOf cause the non-deterministic behavior in workflow?

Here is my use case: I have a Directed Acyclic Graph, I treat vertexes (activities) as processing-node for an object and edges as dependency. Now I want to to walk though the DAG, I start to async process this object with all the vertexes(activities) without any incoming edges, and use Promise.anyOf().get() to get result of the promise list, then loop the promise list, check iscomplete() and merge the result into the object, then I am going to evaluate the graph again, start async-processing next vertext(es) with the updated object if the dependencies are satisfied. I will repeat this process until I finished processing the object with all the vertexes (activities).
This works in my test.
However, I am curious that whether the Promise.anyOf might cause non-deterministic error, because the activity processing time might vary, so the anyOf might return different activity in the retry(is it true? or anyOf will guarantee returning the same activity?) Will it cause the mismatch of the sequence of the Commands in the event history if, for any reason, we have to retry the workflow?

Activity completions are recorded in the workflow event history. Workflow processes (which includes replay) in the order of their appearance in the history. As processing and replay are always in the same order the behavior is deterministic.

1 Like

Thanks for the prompt reply.

How about this scenario:
In the first try of workflow, we run vertexes A, B, C in parallel, then after anyOf(),get(), I check the isCompleted() of A, B, C and I got result of A and B, but C is not finished, we update the object then I run D with updated object… all these will be records in the event history.
Now in retry, we run A, B, C again, is it possible that: when we check isCompleted() and get the result of A and B (from history I guess), we will get the result of C this time? if this is true, then I will call activity D with an updated different object(with result of C) or even different activity E because E is ready to run.
As I am typing these, I guess the workflow will guarantee the result of C is not ready in retry at that time because we are replying the history now. and isComplete() is part of history. Is it right?

You are correct that a naive implementation that checks the result of an activity completion when an activity is invoked would not be deterministic. That’s why Temporal is not implemented this way. It replays history event by event.

So in your scenario. The history will contain completion events for A and B. So when they are replayed first A will become ready and workflow code will run until it is blocked. Then B will become ready and workflow code runs again. At this point D request is produced. Workflow is blocked, so D is sent to the service as a command for execution.

Later, the workflow needs to recover. So, the history is replayed. First A becomes ready and workflow runs, B is ready and the workflow function is given a chance to make progress again. D request is produced. Then D event is processed and checked against the pending command to see if it was produced before. If workflow is deterministic D is in both history and command buffer and the command is deduped. Then C is requested. Note that workflow executes exactly the same codepath on replay as initially.

So C is not recorded in the first run? I thought at least ActivityTaskScheduled of C would be recorded in the history and it would be before the D

Activity results are recorded in the history when they are received. But the workflow is replayed by processing history events and never by looking for results in the history. So in any case the execution is deterministic.