feat(apply): add --concurrency limit for deploy/destroy#504
Conversation
Bound how many resources run a provider lifecycle operation at once.
Defaults to unbounded; defers to Effect's concurrency model via a shared
semaphore that gates only the actual provider call, never the dependency
wait, so a tight limit can never deadlock the DAG.
- `apply(plan, { concurrency })` accepts `"unbounded" | number` (default
`"unbounded"`). The limit is enforced at `instrumentLifecycle`, the single
dispatch chokepoint, so it covers create/update/replace/precreate/delete
across executePlan, converge, and collectGarbage with one shared permit.
- `--concurrency <n>` flag on `deploy` and `destroy` (e.g. `--concurrency 64`).
- Test harness `deploy`/`destroy` accept `ApplyOptions` so the limit is
testable.
- apply.test.ts: unbounded runs all at once; a limit caps the high-water
mark; concurrency 1 serializes; a dependency chain doesn't deadlock under a
tight limit; destroy honors the limit.
|
Install the packages built from this commit: alchemy bun add alchemy@https://pkg.ing/alchemy/a85fd1e@alchemy.run/better-auth bun add @alchemy.run/better-auth@https://pkg.ing/@alchemy.run/better-auth/a85fd1e@alchemy.run/pr-package bun add @alchemy.run/pr-package@https://pkg.ing/@alchemy.run/pr-package/a85fd1e |
Website Preview DeployedURL: https://alchemyeffectwebsite-worker-pr-504-67p2sklcbinzz3cl.testing-2b2.workers.dev Built from commit This comment updates automatically with each push. |
|
@claude please merge latest main into this PR, fix any merge conflicts and push to this pr |
|
Claude finished @sam-goodwin's task in 26m 37s —— View job Unable to push the merge from this runner — but here is everything needed to complete it, including the exact conflict resolutions.
Why I couldn't push the mergeThis workflow runs To let - uses: actions/checkout@v4
with:
fetch-depth: 0 # currently 1 — merge-base with main is unreachable
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--model claude-fable-5
--allowedTools Bash(git fetch:*),Bash(git merge:*),Bash(git checkout:*)Merge analysisI compared both sides against the branch point 1. const applyPlan = dev
? apply(updatePlan, { concurrency: concurrency ?? "unbounded" }).pipe(
Effect.catchCause((cause) =>
Cause.hasInterruptsOnly(cause)
? Effect.failCause(cause)
: Console.error(
`alchemy dev: apply failed; keeping dev alive so healthy resources keep serving.\n${Cause.pretty(cause)}`,
).pipe(Effect.as(undefined)),
),
)
: apply(updatePlan, { concurrency: concurrency ?? "unbounded" });
const outputs = yield* applyPlan;2. return {
name: stackName,
state: stateLayer,
deploy: ((
effect: Effect.Effect<any, any, any>,
applyOptions?: ApplyOptions,
) => buildAndApply(effect, applyOptions)) as ScratchStack<ROut>["deploy"],
plan: ((effect: Effect.Effect<any, any, any>) =>
buildPlan(effect)) as ScratchStack<ROut>["plan"],
destroy: (applyOptions?: ApplyOptions) =>
...(and in the interface, 3. .pipe(
instrumentLifecycle(
limit, // ← add
"delete",
fqn,
node.resource.Type,
logicalId,
old.instanceId,
),
);To finish locallygit fetch origin
git checkout claude/parallelism-deploy-destroy-EYZPn
git merge origin/main # conflicts in deploy.ts + Test/Core.ts → resolve as above
# then apply fix 3 in Apply.ts (it will NOT show up as a conflict)
bun tsc -b
git commit --no-edit && git pushOne thing to watch: main bumped |
Cap how many resources apply at once. Defaults to unbounded.
Programmatic:
The limit is a shared semaphore taken only around the provider call, never around the dependency wait — so a resource blocked on its upstreams holds no permit and a tight limit (even
--concurrency 1) can't deadlock the DAG.