-
-
Notifications
You must be signed in to change notification settings - Fork 513
Description
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:
- Execute the workflow with
items: ["a", "b", "c", "d", "e"] - All 5 activities complete successfully on first execution
- Trigger a replay (e.g. restart the workflow engine, or let durable execution recovery kick in)
- 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: 3to match provider API rate limits - Upgraded to
@effect/workflow0.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: 1as 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)