惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
美团技术团队
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
L
LangChain Blog
雷峰网
雷峰网
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 叶小钗
Engineering at Meta
Engineering at Meta
腾讯CDC
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
有赞技术团队
有赞技术团队
Blog — PlanetScale
Blog — PlanetScale
博客园 - Franky
博客园 - 司徒正美
The Cloudflare Blog
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
C
Check Point Blog
小众软件
小众软件
V
Visual Studio Blog
V
V2EX
F
Full Disclosure
J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
罗磊的独立博客
人人都是产品经理
人人都是产品经理
量子位
Apple Machine Learning Research
Apple Machine Learning Research
F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
博客园_首页
Y
Y Combinator Blog
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
Recorded Future
Recorded Future
G
Google Developers Blog
Vercel News
Vercel News
大猫的无限游戏
大猫的无限游戏
Microsoft Azure Blog
Microsoft Azure Blog
U
Unit 42
爱范儿
爱范儿
Jina AI
Jina AI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Concurrency, Retry, And Timeout Under One Owner
AdmilsonCoss · 2026-05-11 · via DEV Community

AI agents do not fail in one place. They fail across retries, timeouts, tools, and provider calls.

Last time we showed work(items).inParallel(8).withRetry(3).withTimeout("5s").do(fn) — the one-line fluent surface for processing a list. That handles the 80% case.

Check Owned Async Work in TypeScriptPromise.race does not cancel your work and Your AI Is Still Billing After the User Closed the Tab in case you missed them.

This article is about the other 20%: orchestrating heterogeneous tasks that race, fall back, hedge, and retry — with ownership.

Open package.json in enough AI codebases and you'll see combinations like these – or completely different helpers – depending on who wrote the stack:

"p-limit":      "^5.0.0",
"p-map":        "^7.0.0",
"p-retry":      "^6.2.0",
"p-timeout":    "^6.1.2",
"p-queue":      "^8.0.1",
"bottleneck":   "^2.19.5",
"async-retry":  "^1.3.3"

Enter fullscreen mode Exit fullscreen mode

Six libraries is just one example. Some teams roll their own Promise.race loops. Others use async helpers from lodash or bluebird. The pattern is the same: scattered concurrency primitives with no shared ownership.

When a sibling throws, when a timeout fires, when the user hits stop, you have to stitch together queue state, retry delay, timeout wrapper, underlying I/O, cleanup, and error shape yourself.

That is the comparison in this article: not "those tools are useless", but "they are separate primitives." WorkIt's claim is ownership and composition. The runnable benches at the end of each section verify the WorkIt invariants on your machine.

WorkIt has five core composables, all sharing one runtime contract:

run.all      // Promise.all that actually cancels losers on first failure.
run.race     // Promise.race that actually cancels losers.
run.any      // Promise.any that actually cancels remaining tasks.
run.pool     // p-limit + p-map, but children belong to the scope.
run.series   // sequential, with shared cancellation.

Enter fullscreen mode Exit fullscreen mode

Plus four more that compose with them:

run.retry    // backoff with signal-aware sleep.
run.timeout  // deadline that returns a TaskFn.
run.fallback // primary -> secondary, type-safe.
run.hedge    // bounded speculative execution for tail-latency control.

Enter fullscreen mode Exit fullscreen mode

Same familiar names. Different runtime contract: everything below the call belongs to a scope, and the scope owns the cancel.

The composition property under all nine: every WorkIt resilience helper takes a TaskFn<T> and returns a TaskFn<T>. That makes the algebra closed – run.timeout(run.retry(callProvider, 3), "5s") is just function composition. Promise helpers usually return promises or independent wrapper functions, so crossing from timeout to retry to race means you own the glue and the signal threading.


Why run.all improves on Promise.all’s safety

import { run } from "@workit/core";

const [profile, plan, sources] = await run.all([
  (ctx) => fetchProfile({ signal: ctx.signal }),
  (ctx) => planLLM(question, { signal: ctx.signal }),
  (ctx) => retrieveContext(question, { signal: ctx.signal }),
]);

Enter fullscreen mode Exit fullscreen mode

Promise.all rejects on first failure and leaves the other two requests running unless each branch has its own cancellation wiring. Their .then handlers can fire after your error handler already returned a 500, producing completion events that are no longer attached to the owning request.

run.all rejects on first failure and cancels the other two. ctx.signal aborts. defer cleanups run. The reason is typed: CancelReason { kind: "sibling_failed", siblingId, error }.

You can pivot a dashboard on that. You cannot pivot on Error: AggregateError.

Bench 01-run-all-vs-promise-all.mjs. A succeeds at 50 ms. B fails at 30 ms. C succeeds at 100 ms.

Implementation Outer rejected A still ran past reject C still ran past reject Defer ran for losers
Promise.all t=35 ms +16 ms +79 ms n/a
run.all t=32 ms 0 ms (cancelled at +1 ms) 0 ms (cancelled at +1 ms) yes before outer reject

run.race – the race that actually races

const winner = await run.race([callOpenAI, callAnthropic, callGemini]);

Enter fullscreen mode Exit fullscreen mode

Six tokens you wrote with Promise.race. Different runtime contract:

  • Each body receives a ctx.signal linked to the race.
  • First settlement cancels the rest at the AbortSignal boundary, before TCP completes.
  • Each loser sees CancelReason { kind: "race_lost", winnerId } — typed, exhaustively narrowed.
  • await run.race(...) returns only after losers have finished cleaning up.

Bench 02-run-race-vs-promise-race.mjs. Anthropic at 10 ms, OpenAI at 50 ms, Gemini at 80 ms.

Implementation Winner at OpenAI loser still ran Gemini loser still ran Loser reason
Promise.race t=14 ms +47 ms (61 ms total) +77 ms (91 ms total) none
run.race t=17 ms 0 ms (cancelled at t=16 ms) 0 ms (cancelled at t=16 ms) race_lost

That loser runtime x N parallel agents x P requests per second is the line on your invoice that nobody wrote.


run.any – first success, rest cancelled

const cheapest = await run.any([callExpensive, callCheap, callCheaper]);

Enter fullscreen mode Exit fullscreen mode

Promise.any resolves with the first success and ignores the rest. The slower siblings keep running. The faster failing ones got logged and forgotten. run.any does the same – except the slower siblings actually stop.

Bench 03-run-any-vs-promise-any.mjs. A fails at 30 ms. B succeeds at 50 ms. C succeeds at 100 ms.

Implementation Resolved at C kept running Defer ran for C
Promise.any t=61 ms +47 ms (108 ms total) n/a
run.any t=65 ms 0 ms (cancelled at t=65 ms) yes

run.pool – bounded concurrency that cancels

const results = await run.pool(8, files.map((file) => async (ctx) => {
  return uploadOne(file, { signal: ctx.signal });
}));

Enter fullscreen mode Exit fullscreen mode

p-limit(8) is a semaphore. That's useful, and current versions can clear pending queue items when you ask them to. What it is not is a structured scope: it does not automatically turn a sibling failure into in-flight cancellation, typed cancel reasons, cleanup, and a partial-result contract.

run.pool(8, tasks) is a semaphore + a scope. Default policy is Promise.all-style fail-fast: first throw cancels queued and in-flight. Results are positionally indexed regardless of completion order. Switch policy with one line and the return type changes so you can't ignore failures:

const out = await work(files).inParallel(8).onError("collect").do(uploadOne);

if (out.mode === "collect") {
  for (const r of out.results) {
    if (r.status === "rejected") logFailure(r.reason);
  }
}

Enter fullscreen mode Exit fullscreen mode

WorkOutput<R> is a discriminated union – mode: "fail" | "continue" | "collect". Change .onError("continue") and the return type forces you to handle errors[]. The compiler is your audit log.

Bench 04-pool-vs-semaphore.mjs. 10 items, concurrency 4. Item 3 throws at 20 ms; the rest take 100 ms each.

Implementation Outer rejected Started Fulfilled AFTER rejection Cancelled Never started Longest post-rejection run
local pLimitLike(4) semaphore baseline t=31 ms 10 9 0 0 +295 ms
run.pool(4, ...) t=33 ms 4 0 3 6 0 ms

295 ms of post-rejection work, multiplied across a fleet, becomes avoidable runtime and provider cost.


run.retry – composable, cancel-aware backoff

const callWithRetry = run.retry(callProvider, {
  times: 4,
  backoff: "exponential",
  initialDelay: "200ms",
  maxDelay: "5s",
  jitter: true,
  retryIf: (err) => isTransient(err),
});

const answer = await callWithRetry(ctx);

Enter fullscreen mode Exit fullscreen mode

Three things WorkIt makes part of the retry contract:

  1. Stop retrying on scope cancellation. When the parent scope cancels mid-attempt, run.retry does not enqueue another attempt. The task settles as cancelled, not failed.
  2. Validate input at the boundary. run.retry({ times: 1e9 }) would create an unbounded retry policy. run.retry rejects it: RangeError: retry attempts must be an integer between 1 and 1000. Bound is MAX_RETRY_ATTEMPTS.
  3. Sleep with the scope signal. Backoff sleep is interruptible – abort the signal, the sleep rejects, the loop exits. The benchmark below compares against a signal-unaware retry loop; current retry libraries may expose their own abort hooks, but they still do not own WorkIt's scope tree, cleanup, and cancel-reason contract.

Bench 05-retry-on-cancel.mjs. Body throws on every attempt. External cancel fires around t=50 ms. Up to 8 retries with 50 ms backoff.

Implementation Cancel observed Outer settled Cancel latency Extra attempts after cancel Settled as
signal-unaware retry loop t=63 ms t=701 ms 638 ms 7 rejected
run.retry t=61 ms t=61 ms 0 ms 0 cancelled (kind: manual)

638 ms of wasted retry work after the user already cancelled. Per request. Multiply by the agent fan-out.


run.timeout– composes with retry, race, and pool

const fastest = await run.race([
  run.timeout(callPrimary,   "800ms"),
  run.timeout(callSecondary, "800ms"),
]);

Enter fullscreen mode Exit fullscreen mode

run.timeout(task, "800ms") returns a TaskFn. It composes. You can wrap it in run.retry. You can put it inside run.race. You can hand it to run.pool. The signature is closed under composition.

Promise timeout helpers return promises or decorated promises. Some expose AbortSignal support. They still do not return a WorkIt TaskFn, so crossing timeout, retry, race, pool, and cleanup means you own the composition boundary.


run.fallback – primary, secondary, type-safe

const callWithFallback = run.fallback(
  run.retry(callProvider, 3),
  callBackupProvider,
);

Enter fullscreen mode Exit fullscreen mode

Primary fails (after retries) -> secondary runs. Same ctx.signal. Same scope. Same cancel reason if the parent stops. No nested try/catch. No "did I forget to await the fallback" Slack message at 2 a.m.


run.supervise – restart policy for long-lived work

run.retry is for one operation that may fail transiently and then succeed. run.supervise is for a long-lived task – a heartbeat, a queue consumer, a connection watcher, an agent keep-alive – that may need restart semantics with bounded backoff.

import { run } from "@workit/core";

const result = await run.supervise(async () => {
  attempts++;
  if (attempts < 3) throw new Error("transient worker failure");
  return "stable";
}, {
  restartOn:    "error",
  maxRestarts:  3,
  backoff:      () => 1,
});

Enter fullscreen mode Exit fullscreen mode

// samples/supervision.sample.js – asserted in CI:
//   result === "stable"
//   attempts === 3

Enter fullscreen mode Exit fullscreen mode

The supervised body fails twice, restarts each time under the policy, and stabilises on the third attempt. The parent scope can still cancel everything at once, and the cancel reason carries down through the supervision wrapper. Restart policies cap at maxRestarts per resetWindow so a permanently broken body doesn't infinite-loop.

npm run sample:supervise

Enter fullscreen mode Exit fullscreen mode

The decision rule: use run.retry for a single call that can hiccup; use run.supervise for a process that should keep running.


run.hedge – bounded speculative requests

const ranked = await run.hedge(
  (ctx) => reranker.rank(question, sources, { signal: ctx.signal }),
  { after: "2s", max: 2 },
);

Enter fullscreen mode Exit fullscreen mode

If the first call hasn't returned in 2 seconds, fire a second one. First success wins; the rest cancel. Bounded by max, this is a measured way to reduce tail latency without paying for every speculative fan-out.

Bench 06-hedge-tied-requests.mjs. Two scenarios, opts { after: "50ms", max: 3 }.

Scenario Body latency Attempts fired (timestamps) Winner Losers cancelled Cancel reason
slow 200 ms 3 (t=2 ms, 62 ms, 107 ms) id=1 at 217 ms 2 race_lost
fast 30 ms 1 (no hedge fired) id=1 at 31 ms 0 n/a

The fast path doesn't pay for hedging at all. The slow path bounded by max. Every loser tagged with race_lost.


Side-by-side – who actually cancels

// 3 tasks. B fails at 30 ms. A succeeds at 50 ms. C succeeds at 100 ms.

await Promise.all([A, B, C]);     // rejects at 30 ms. A and C keep running for ~16/63 ms.
await Promise.race([A, B, C]);    // rejects at 30 ms. A and C keep running.
await Promise.any([A, B, C]);     // resolves at 50 ms. C keeps running for ~44 ms.

await run.all([A, B, C]);         // rejects at 30 ms. A and C cancelled in 1 ms, defer ran.
await run.race([A, B, C]);        // rejects at 30 ms. A and C cancelled in 0-1 ms.
await run.any([A, B, C]);         // resolves at 50 ms. C cancelled in 0 ms, defer ran.

Enter fullscreen mode Exit fullscreen mode

Same shape. Different contract. Native primitives return a value. WorkIt primitives own the tree underneath the value.


How do other libraries compare

Tool Cancels on sibling failure Signal-aware retry Composable timeout (returns TaskFn) Hedged requests Bundle
WorkIt yes yes yes yes built-in 14,175 B / 4,835 B gz for all nine composables
Promise.all / race / any no n/a n/a n/a 0
p-limit + p-retry + p-timeout partial/manual wiring partial/manual wiring separate abstractions no three deps
RxJS yes on unsubscribe partial via operators yes via operators no large
Effection yes structured (generator ops) yes yes no medium
Effect-TS yes structured (fibers + typed Cause) yes yes no large

For simple array processing where nothing else can fail, p-limit is fine. For full-stack apps where you want a broader effect or operation model, Effection and Effect-TS are solid. WorkIt's distinction is narrower: structured-concurrency composition without leaving async/await, with nine composables in one ownership tree and the bundle size shown above.


Receipts

The WorkIt runtime claims above are verified by either npm run verify (the production gate) or npm run bench:articles (the side-by-side suite that produced the representative timing tables in this article).

npm run bench:articles
# full article suite: 19 passed, 0 failed

Enter fullscreen mode Exit fullscreen mode

This article cites benches 01-06 from the full suite. Each bench script is ~100 lines, zero external dependencies, and asserts the WorkIt invariant in-line. Timings are representative captured runs; the assertions guard semantic invariants, not exact milliseconds. The folder has its own package.json so the published package's dependency graph stays empty. Read the README at benchmarks/articles/ for how the promise-helper baselines stay honest.

Production-side gates that back the same composables:

Claim Evidence
Cancellation safety, all composables Benches 01-06 plus tests/evidence/lifecycle/owned-work.mjs verify parent cancellation, sibling failure, retry cancellation, race loser cleanup, and owned background work.
run.retry validation RangeError for times <= 0, > 1000, NaN, Infinity, fractional. Identical rejection on numeric and object form.
run.race / run.any loser cleanup LIFO defer blocks observed in test for every loser; outer promise does not resolve until cleanup completes
Bundle of all nine composables Included in 14,175 B min / 4,835 B gzip core-group-import. Tree-shaken if unused.

What's next

Nine composables share one engine. Tomorrow we open the engine.

We'll look at what cooperative cancellation can do, what it cannot do, and where the hard boundary starts. Then we put a CPU spin loop that ignores every signal in front of offload({ timeout: "200ms" }) and verify that worker termination prevents a late marker file from appearing. The CI gate runs stat() on it.

AbortController cannot preempt a CPU loop. WorkIt cannot change that language boundary, but a worker thread can be terminated by its host.


Source, Benchmarks, And Evidence