Temporal Signal

A Signal is a message sent asynchronously to a running Workflow Execution which can be used to change the state and control the flow of a Workflow Execution. It can only deliver data to a Workflow Execution that has not already closed. To receive data from a Workflow, you can use a Query.

Signals are useful for Workflows that must react to external events, like waiting for a human to complete a task. Here are some other examples of when a developer might use a Signal in a Temporal application:

  • User Interactions in Web Applications: An online retailer that uses Temporal Workflows to manage orders could use Signals to let customers modify them. For example, when a user clicks a link or button to change the delivery date, the web application could use a Signal to pass the new date to the Workflow Execution responsible for processing that order.
  • Processing Events: In an order processing system, a Signal can be sent to a Workflow when payment is confirmed, allowing the Workflow to proceed with the next steps like shipping the order.
  • Frequently Changing Data Interactions: Workflows that depend on frequently changing data, such as stock trading dashboards, can use Signals to receive the latest data and then take a different execution path based on this data.

Signals are a powerful feature in Temporal, enabling Workflows to be dynamic and responsive to external events.

Developing Signals

There are two steps for adding support for a Signal to your Workflow code:

  • Defining the Signal - You define the method within the Workflow Interface which specifies the name, return type, and parameters passed via the Signal.
  • Handling the Signal - You implement the method that will be invoked when the Signal is received from a Temporal Client.
import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;
import io.temporal.workflow.SignalMethod;
 
@WorkflowInterface
public interface MyWorkflow {
 
  @WorkflowMethod
  String workflow(String input);
 
  @SignalMethod
  void joinSignal(String userID, String groupID);
 
}

Waiting for Signals

Signals in Temporal allow Workflows to react to external events without disrupting their ongoing execution - that is, Signals are not designed to necessarily block the overall Workflow Execution. The Workflow continues to execute its logic independently of when or whether a Signal will be received. However, if required, a Workflow can be designed to pause its execution until a specific Signal is received. This is achieved using Workflow.await(), a method imported from the Java SDK which will stop the Workflow’s progress until a certain condition is fulfilled. Using Workflow.await() within the body of a Workflow to pause a Workflow until a certain condition is met is a very common pattern.

Sending Signal

public class SignalClient {
  public static void main(String[] args) throws Exception {
    WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
    WorkflowClient client = WorkflowClient.newInstance(service);
    SignalWorkflow handle = client.newWorkflowStub(SignalWorkflow.class, "workflow-id-123");
    handle.joinSignal("user-1", "group-1");
    System.exit(0);
  }
}

From command-line:

$ temporal workflow signal \
  --workflow-id="helloSignal" \
  --name="updateGreeting" \
  --input=\"Bye\"

From within a Workflow:

public String signalingWorkflow() {
  MyWorkflow workflow = Workflow.newExternalWorkflowStub(MyWorkflow.class, "workflow-id-123");
  workflow.joinSignal("user-1", "group-1");
  return "Success";
}

Signal-With-Start

In some cases, you might need to send a Signal, but aren’t sure whether the Workflow Execution is running yet. The Signal-with-Start feature in Temporal provides a solution for this.

This feature checks if there is currently a running Workflow Execution with the given Workflow ID. If the Workflow ID exists, then it will be signaled. Otherwise, a new Workflow Execution is started and immediately sent the Signal. This feature is useful in scenarios where you want to ensure that a Workflow is running and that it receives specific information right from the beginning.

Signal-With-Start is a Client method that takes the following arguments:

  • Workflow type
  • A Workflow ID
  • Workflow input
  • A Signal type
  • Signal input
  • Task Queue
import io.temporal.client.BatchRequest;
 
...
 
public static void signalWithStart() {
  WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
 
  WorkflowClient client = WorkflowClient.newInstance(service);
  
  WorkflowOptions optons = WorkflowOptions.newBuilder()
      .setWorkflowId("workflow-id-123")
      .setTaskQueue("my-taskqueue")
      .build();
 
  MyWorkflow workflow = client.newWorkflowStub(MyWorkflow.class, options);
 
  BatchRequest request = client.newSignalWithStartRequest();
  request.add(workflow::joinSignal, "user-1", "group-1");
  client.signalWithStart(request);
}