---
title: "Interruptible execution"
description: "Stop long-running work safely when a durable interrupt arrives."
canonical_url: "https://docs.superdurable.io/design-patterns/interruptible/"
source: "docs/content/design-patterns/interruptible.mdx"
locale: "en"
---

# Interruptible execution

Stop long-running work safely when a durable interrupt arrives.

Use this pattern for work that repeats for a long time, such as batch processing. To wait for an external system, use [Polling](https://docs.superdurable.io/design-patterns/polling.md) instead. A user can request an interrupt while the work is running. Each branch finishes its current unit of work, sees the durable signal before its next loop iteration, and then completes safely.

**InterruptibleFlow** starts **WorkA** and **WorkB** in parallel. Each Step checks the durable **interruptSignal** before scheduling its next unit of work. The **interrupt** RPC sets that signal, letting either branch finish the Flow gracefully.

**Flow: InterruptibleFlow**

Start Step: `Init`

**Steps**

- `Init`
- `WorkA`
  - WaitFor `until`: timer `2 seconds timer`
  - `gracefulComplete` when `(self.interrupt_signal.get(context) or '') == INTERRUPT_VALUE`
  - `gracefulComplete` when `not ((self.interrupt_signal.get(context) or '') == INTERRUPT_VALUE) and input.progress > input.job_upper_bound`
- `WorkB`
  - WaitFor `until`: timer `3 seconds timer`
  - `gracefulComplete` when `(self.interrupt_signal.get(context) or '') == INTERRUPT_VALUE`
  - `gracefulComplete` when `not ((self.interrupt_signal.get(context) or '') == INTERRUPT_VALUE) and input.progress > input.job_upper_bound`

**Edges**

- `goToMany` (decision; Init) → `WorkA` (step): fan-out
- `goToMany` (decision; Init) → `WorkB` (step): fan-out
- `goTo` (decision; WorkA; when `not ((self.interrupt_signal.get(context) or '') == INTERRUPT_VALUE) and not (input.progress > input.job_upper_bound)`) → `WorkA` (step): go_to
- `goTo` (decision; WorkB; when `not ((self.interrupt_signal.get(context) or '') == INTERRUPT_VALUE) and not (input.progress > input.job_upper_bound)`) → `WorkB` (step): go_to
- `interruptSignal` (attribute) → `WorkA` (step): get
- `interruptSignal` (attribute) → `WorkB` (step): get
- `interrupt` (rpc) → `interruptSignal` (attribute): set

Example: [examples/python/dex_examples/patterns/interruptible/interruptible_execution_flow.py](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/patterns/interruptible/interruptible_execution_flow.py)

## Core implementation

Each sample shows **WorkA**. It waits between units of work, completes when the interrupt signal arrives, and otherwise schedules its next loop iteration. **WorkB** follows the same pattern. The runnable examples also include Flow registration and the **interrupt** RPC.

**Python**

```python
class WorkA(Step[WorkJobParametersInput]):
    def __init__(self, interrupt_signal: Attribute[str]) -> None:
        self.interrupt_signal = interrupt_signal

    def wait_for(self, context: Context, input: WorkJobParametersInput) -> Wait:
        return Wait.until(Timer.by_duration(timedelta(seconds=2)))

    def execute(
        self,
        context: Context,
        input: WorkJobParametersInput,
    ) -> StepDecision:
        if (self.interrupt_signal.get(context) or "") == INTERRUPT_VALUE:
            print("A: Interrupted!")
            return graceful_complete()

        if input.progress > input.job_upper_bound:
            print("WorkA completed")
            return graceful_complete()

        print(
            f"[{context.flow_id}][{context.step_execution_id}]: "
            f"Doing job {input.progress}"
        )
        return go_to(
            WorkA,
            WorkJobParametersInput(input.job_upper_bound, input.progress + 1),
        )
```

Example: [examples/python/dex_examples/patterns/interruptible/interruptible_execution_flow.py](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/patterns/interruptible/interruptible_execution_flow.py)

**Go**

```go
type workA struct {
	dex.StepDefaults
}

func (workA) WaitFor(
	ctx dex.Context,
	input WorkJobParametersInput,
) (*dex.Wait, error) {
	return dex.Until(dex.Timer(1500 * time.Millisecond)), nil
}

func (workA) Execute(
	ctx dex.Context,
	input WorkJobParametersInput,
) (*dex.StepDecision, error) {
	signal, err := InterruptSignal.Get(ctx)
	if err == nil && signal == "cancel" {
		fmt.Println("A: Interrupted!")
		return dex.GracefulComplete(nil), nil
	}
	if input.Progress > input.JobUpperBound {
		fmt.Println("WorkA completed")
		return dex.GracefulComplete(nil), nil
	}
	fmt.Printf(
		"[%s][%s]: Doing job %d\n",
		ctx.FlowID(),
		ctx.StepExecutionID(),
		input.Progress,
	)
	next := WorkJobParametersInput{
		JobUpperBound: input.JobUpperBound,
		Progress:      input.Progress + 1,
	}
	return dex.GoTo(workA{}, next), nil
}
```

Example: [examples/go/patterns/interruptible/workflow.go](https://github.com/superdurable/dex/tree/main/examples/go/patterns/interruptible/workflow.go)

**Java**

```java
final class WorkA implements Step<WorkJobParametersInput> {
    @Override
    public Class<WorkJobParametersInput> getInputType() {
        return WorkJobParametersInput.class;
    }

    @Override
    public Wait waitFor(final Context context, final WorkJobParametersInput input) {
        return Wait.until(Timer.byDuration(Duration.ofMillis(1500)));
    }

    @Override
    public StepDecision execute(
            final Context context,
            final WorkJobParametersInput input) {
        final String signal = interruptSignal.get(context);
        if (signal != null && signal.equals("cancel")) {
            System.out.println("A: Interrupted!");
            return StepDecision.gracefulComplete();
        }

        if (input.progress > input.jobUpperBound) {
            System.out.println("WorkA completed");
            return StepDecision.gracefulComplete();
        }
        System.out.printf(
                "[%s][%s]: Doing job %s%n",
                context.getFlowId(),
                context.getStepExecutionId(),
                input.progress);

        final WorkJobParametersInput next =
                new WorkJobParametersInput(input.jobUpperBound, input.progress + 1);
        return StepDecision.goTo(WorkA.class, next);
    }
}
```

Example: [examples/java/src/main/java/io/superdurable/dex/patterns/interruptible/InterruptibleFlow.java](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/patterns/interruptible/InterruptibleFlow.java)

**TypeScript**

```typescript
class WorkA implements Step<WorkJobParametersInput> {
  public constructor(private readonly flow: InterruptibleFlow) {}

  public getStepType(): string {
    return "WorkA";
  }

  public waitFor(_context: Context, _input: WorkJobParametersInput): Wait {
    return Wait.until(Timer.byDuration(1_000));
  }

  public execute(context: Context, input: WorkJobParametersInput): StepDecision {
    const signal = this.flow.interruptSignal.get(context);
    if (signal === "cancel") {
      console.log("A: Interrupted!");
      return gracefulComplete();
    }

    if (input.progress > input.jobUpperBound) {
      console.log("WorkA completed");
      return gracefulComplete();
    }

    console.log(
      `[${context.flowId}][${context.stepExecutionId}]: Doing job ${input.progress}`,
    );

    return goTo(WorkA, {
      jobUpperBound: input.jobUpperBound,
      progress: input.progress + 1,
    });
  }
}
```

Example: [examples/typescript/src/patterns/interruptible/interruptible-execution-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/patterns/interruptible/interruptible-execution-flow.ts)

**Rust**

```rust
#[derive(Default)]
struct WorkA;

impl Step for WorkA {
    type Input = WorkJobParametersInput;

    fn wait_for(&self, _context: &mut Context, _input: Self::Input) -> HandlerResult<Wait> {
        Ok(Wait::until(Timer::by_duration(Duration::from_secs(1))))
    }

    fn execute(&self, context: &mut Context, input: Self::Input) -> HandlerResult<StepDecision> {
        if interrupt_signal().get(context)?.as_deref() == Some("cancel") {
            println!("A: Interrupted!");
            return Ok(StepDecision::graceful_complete(()));
        }
        if input.progress > input.job_upper_bound {
            println!("WorkA completed");
            return Ok(StepDecision::graceful_complete(()));
        }
        println!(
            "[{}][{}]: Doing job {}",
            context.flow_id(),
            context.step_execution_id(),
            input.progress
        );
        Ok(StepDecision::go_to(
            &WorkA,
            WorkJobParametersInput {
                job_upper_bound: input.job_upper_bound,
                progress: input.progress + 1,
            },
        ))
    }
}
```

Example: [examples/rust/src/patterns/interruptible/flow.rs](https://github.com/superdurable/dex/tree/main/examples/rust/src/patterns/interruptible/flow.rs)
