Skip to content

@effect/workflow: Effect.all with concurrency > 1 wrapping Activity.make causes deadlock during replay #6014

@Necmttn

Description

@Necmttn

What version of Effect is running?

  • effect: 3.19.15
  • @effect/workflow: 0.16.0
  • @effect/platform: 0.94.2
  • @effect/cluster: 0.56.1
  • Runtime: Bun 1.3.0

What steps can reproduce the bug?

When a workflow uses Effect.all(activities, { concurrency: N }) where N > 1 and each element is an Activity.make(...), the workflow completes successfully on first execution but deadlocks during durable replay (e.g. when the workflow engine re-executes the workflow to recover state).

Minimal reproduction pattern:

import { Activity, Workflow } from "@effect/workflow";
import { Effect, Schema, Schedule } from "effect";

const MyWorkflow = Workflow.make({
  name: "DeadlockRepro",
  payload: Schema.Struct({ items: Schema.Array(Schema.String) }),
  success: Schema.String,
  execute: ({ items }) =>
    Effect.gen(function* () {
      // Create an array of activities, one per item
      const activities = items.map((item) =>
        Activity.make({
          name: `process:${item}`,
          success: Schema.String,
          error: Schema.String,
          execute: Effect.succeed(`done:${item}`),
        })
      );

      // This works with concurrency: 1, deadlocks with concurrency > 1 during replay
      const results = yield* Effect.all(activities, { concurrency: 3 });
      return results.join(",");
    }),
});

Steps to reproduce:

  1. Execute the workflow with items: ["a", "b", "c", "d", "e"]
  2. All 5 activities complete successfully on first execution
  3. Trigger a replay (e.g. restart the workflow engine, or let durable execution recovery kick in)
  4. During replay, the workflow hangs indefinitely — activities never resume

The same Effect.all({ concurrency: 3 }) pattern works perfectly outside of @effect/workflow (i.e. without Activity.make), confirming the issue is in the Activity replay mechanism interacting with concurrent suspension.

What is the expected behavior?

Effect.all(activities, { concurrency: N }) where N > 1 should work correctly both during first execution and during replay. Activities should be replayed from the durable log and the concurrent combinator should resume all branches.

What do you see instead?

The workflow hangs indefinitely during replay. No error is thrown, no timeout fires at the workflow level — the activities simply never complete. The workflow fiber appears to be suspended waiting for activity results that are never replayed.

With concurrency: 1 (sequential execution), the same workflow replays correctly.

Additional information

History:

  • Designed concurrency: 3 to match provider API rate limits
  • Upgraded to @effect/workflow 0.15.2 which included PR ensure no more Activites are attempted before suspending #5880 ("ensure no more Activities are attempted before suspending") — believed to fix this
  • Upgraded to 0.16.0, restored concurrency: 3 — deadlock persisted during replay
  • Reverted to concurrency: 1 as workaround

Investigation (2026-01-30): Created a standalone test that runs the same provider layer stack (CredentialService + RateLimitedHttpClient + NotionProvider) with Effect.all({ concurrency: 3 }) but without any @effect/workflow or Activity machinery. Result: 14/14 pages fetched successfully in both sequential and concurrent modes. No hang. This confirms the service layer is not the cause — the deadlock is specifically in the Activity replay mechanism when combined with concurrent Effect.all.

Workaround: Use concurrency: 1 (sequential) for all Effect.all/Effect.forEach calls that wrap Activity.make. This avoids the deadlock but sacrifices throughput.

Related: #5876 (different symptom — unhandled error logging — but same @effect/workflow Activity area)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions