Cancellation Equivalent of Golang SDK

In this doc: https://docs.temporal.io/docs/workflows, there’s a code snippet about a Subscription Workflow. The code snippet includes a try catch block: catch (CancellationException e).

Is this CancellationException one thrown by Temporal, or one thrown by the application? If thrown by Temporal, is there an equivalent construct in the Golang SDK? How does this work? If it is thrown by the application, then I think I understand.

What if someone cancels the workflow via another Temporal client (front-end, CLI, etc.)? If we wanted to kick off activities as a result of this forced workflow cancellation, we’d have to wrap this subscription workflow in a parent workflow right?

Btw, I saw this here: Design patterns for cleanup during cancellation, and I didn’t think it was identical so hence the new post.

1 Like

Java and Go use very different approaches for cancellation. The reason is that Go has a standard mechanism to cancel function execution through Context. Java doesn’t have any standard way to cancel the execution of some code. Thread interrupt and the related exceptions are more like an annoyance than a real cancellation mechanism.

Go

Go uses workflow.Context for cancellation. workflow.Context behaves exactly as the standard context.Context with the only difference is that Done() method returns the Channel interface instead of the native Go channel. To cancel an activity for example cancel the context that was used to invoke that activity:

	ctx, cancelFunc := workflow.WithCancel(ctx)
	err := workflow.ExecuteActivity(ctx, foo).Get(null)
	if _, ok := err.(*CanceledError); ok {
                // Activity was canceled
	}

	...
	// Trigger cancellation of activity context from a different goroutine
	cancelFunc()

Java

As Java doesn’t have any standard patterns around code cancellation Temporal introduced its own mechanism. Any part of the workflow code can be made cancellable by wrapping it in a CancellationScope. Then call to CancellationScope.cancel() will cancel the wrapped code.

      CancellationScope scope = Workflow.newCancellationScope(() -> {
         try {
             activity.foo())
         } catch (CanceledFailure e) {
             // Activity was canceled
         }
      });
      scope.run();
      ...
      // From a different thread (for example signal method):
      scope.cancel();

Workflow Cancellation

The above samples demonstrate explicit cancellation requested by the workflow code itself. What does happen when a whole workflow gets a cancellation request through tctl or any other client?

In the case of Go, the root workflow context passed to the workflow function gets canceled.

In the case of Java, the root CancellationScope is canceled. The main workflow method (annotated with @WorflowMethod) is always invoked in the context of a root CancellationScope. So any cancellable code in the workflow gets notified when the root context is canceled.

Cleanup

It is not possible to execute any activities using a canceled context in the case of Go. They will immediately fail with CanceledError. It is also not possible to invoke any activities inside a canceled CancellationScope in the case of Java. Any such invocation will immediately throw CanceledFailure.

This represents a problem that canceled workflow cannot execute any cleanup logic that executes activities. The solution is to use a disconnected context in Go (created through workflow.NewDisconnectedContext) and a disconnected cancellation scope (created through Workflow.newDisconnectedScope) in Java for such cleanup operations.