Add experimental idempotent start hooks#2684
Conversation
🦋 Changeset detectedLatest commit: f0dae38 The changes in this PR will be included in the next version bump. This PR includes changesets to release 21 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests💻 Local Development (1 failed)nextjs-webpack-canary (1 failed):
Details by Category✅ ▲ Vercel Production
❌ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
❌ Some E2E test jobs failed:
Check the workflow run for details. |
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 10 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 25 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 50 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 10 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 25 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) workflow with 50 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) stream pipeline with 5 transform steps (1MB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) 10 parallel streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) fan-out fan-in 10 streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
0c1320a to
de1af62
Compare
- Bump experimentalStartHookLoserAck capability cutoff to 5.0.0-beta.27 (beta.26 was published from main without this change) and rename it away from colliding with World.experimentalStartHookAdmission - Derive WorkflowStartError.queued from stage (collapses the options union and its serialized/reducer/reviver/web-hydration copies) - Share ExperimentalStartHookSchema/type from @workflow/world; align the queue-side token validation (min(1)) with the event schema - Classify 409 hook_conflict inside the shared errorForResponse so v3 and v4 request paths map it identically - Postgres: extract getOwnStartHookClaim/claimStartHookTokenInTx (the two run-creation paths had drifted), parallelize hook/claim snapshot reads, settle disposed-hook claims with guarded statements instead of SELECT-then-branch, drop the retention transaction (statements touch disjoint rows), and GC expired claim debris of terminal runs - Local: one claim lock per token (reclaim and materialize now mutually exclude), expiry pre-check before taking the reclaim lock, shared settleClaimForDisposedHook, parallel tokens-dir sweep that skips already-settled claims and GCs expired debris - start(): hoist repeated enqueue/create calls, contractError helper, drop the positional verifyRunId flag; stop logging raw hook tokens
Refs #2376 (gaps 1 and 2). Paired server PR: https://github.com/vercel/workflow-server/pull/561.
Adds experimental start-hook idempotency:
start()can atomically reserve a hook token as part of run admission, so duplicate starts are rejected before any duplicate workflow code can run.HookConflictErrorwith the owningconflictingRunId.run_createdevent + token claim) commits atomically before the run is queued; if queueing then fails,start()cancels the run, which releases the unmaterialized claim so the caller can retry immediately.run_createdevent, and token claim. A queued run that loses admission acknowledges without running user code (turbo mode is disabled for start-hook runs). If the queue accepts but admission cannot be confirmed,start()throws the newWorkflowStartErrorcarryingrunId/stage/queued/retryable.createHook({ token })as usual; after disposal or run completion/failure the claim is retained until the TTL so duplicates stay fenced. Cancellation releases claims that never materialized.Docs Preview
start()WorkflowStartError