How does workflow thread synchronization work?

Hello,

Documentation says There is no need in explicit synchronization because multi-threaded code inside a workflow is executed one thread at a time and under a global lock.

There is also the following documentation in DeterministicRunner.java:

/**
   * Executes a runnable in a specially created workflow thread. This newly created thread is given
   * chance to run before any other existing threads. This is used to ensure that some operations
   * (like signal callbacks) are executed before all other threads which is important to guarantee
   * their processing even if they were received after workflow code decided to complete. To be
   * called before runUntilAllBlocked.
   */
  void executeInWorkflowThread(String name, Runnable r);

With this code:

private final List<Object> jobQueue = new ArrayList<>();

@WorkflowMethod
@Override
public void executeCreateTree(@Nonnull List<Object> jobQueue) {
    this.jobQueue.addAll(jobQueue.getJobsList());

    Workflow.await(() -> this.jobQueue.size() > 0);

    Object jobToExecute = this.jobQueue.get(0);
    this.jobQueue.remove(0);

    if (this.jobQueue.size() > 0) {
        Workflow.continueAsNew(this.jobQueue);
    } else {
        // Exit workflow
    }
}

@Override
@SignalMethod
public void scheduleCreateTree(@Nonnull Object jobInfo) {
    this.jobQueue.add(jobInfo);
}

How does Temporal Java SDK guarantee that jobQueue did not change between this.jobQueue.size() > 0 and Workflow.continueAsNew(this.jobQueue); lines? DeterministicRunner documentation indicates that SignalMethod will be given a chance to run before workflow completes. And that would indicate that last signal could be lost as workflow would decide to exit.

In general it is possible for thread to be interrupted in between those 2 lines. In a typical java application I would put both if-clause and Workflow.continuesAsNew inside of a single critical section. But it seems to be not recommended here.

Could you please advise?

How does Temporal Java SDK guarantee that jobQueue did not change between this.jobQueue.size() > 0 and Workflow.continueAsNew(this.jobQueue); lines? DeterministicRunner documentation indicates that SignalMethod will be given a chance to run before workflow completes. And that would indicate that last signal could be lost as workflow would decide to exit.

Temporal uses cooperative multithreading to provide this guarantee as well as ensure deterministic execution of multithreaded code. Only one thread is executed at a time and switch to another thread can happen only when a blocking operation using Temporal provided primitives is called. Examples of such operations are Workflow.sleep, Promise.get, Workflow.await. As no blocking operations are called between this.jobQueue.size() > 0 and Workflow.continueAsNew(this.jobQueue); lines, no other thread can intervene.

In general it is possible for thread to be interrupted in between those 2 lines. In a typical java application I would put both if-clause and Workflow.continuesAsNew inside of a single critical section. But it seems to be not recommended here.

It is not recommended as standard Java synchronization can block threads in places that Temporal doesn’t have control over. It leads to deadlocks. Here is an issue to add a deadlock detector to make troubleshooting such programming mistakes easier.

1 Like

Cool, thank you very much.

2 Likes

I am trying to summarize my learning here: How does multi-threading works in Cadence/Temporal workflow? - Stack Overflow

1 Like