<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib" -->

---
title: The Tokio/Rayon Trap and Why Async/Await Fails Concurrency
description: async/await is criticized for conflating asynchrony with concurrency, creating production pitfalls like the Tokio/Rayon boundary problem where developers must...
canonical: https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: The Tokio/Rayon Trap and Why Async/Await Fails Concurrency | daily.dev
og:description: async/await is criticized for conflating asynchrony with concurrency, creating production pitfalls like the Tokio/Rayon boundary problem where developers must...
og:url: https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib
og:image: https://api.daily.dev/og/posts/V76C9exiB.png
og:image:alt: The Tokio/Rayon Trap and Why Async/Await Fails Concurrency
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The Tokio/Rayon Trap and Why Async/Await Fails Concurrency

**[Hacker News](https://daily.dev/sources/hn)** · 7 min read · 11 upvotes · 1 comments

## Summary

async/await is criticized for conflating asynchrony with concurrency, creating production pitfalls like the Tokio/Rayon boundary problem where developers must manually partition I/O and CPU-bound work. Key failure modes include cooperative executor stalls from CPU-bound tasks, unbounded task spawning leading to OOM crashes, and work-stealing schedulers destroying cache locality at scale. As an alternative, the author introduces Project Tina: a thread-per-core, shared-nothing concurrency framework using explicit state machines (Isolates), strictly bounded mailboxes, and deterministic simulation testing — trading async magic for structural predictability.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://pmbanugo.me/blog/why-async-await-complect-concurrency>

## Questions this post answers

### Why does mixing Tokio and Rayon cause problems in production Rust applications?

Mixing Tokio and Rayon requires manually partitioning code into I/O-bound and CPU-bound functions and orchestrating message-passing between the two runtimes, since Tokio's cooperative executor stalls when a task runs CPU-heavy work like parsing a large JSON payload without yielding at an await point. Engineering teams at PostHog and Meilisearch have documented the operational pain of maintaining this boundary and preventing deadlocks between the two schedulers.

_Anyone untangling Tokio and Rayon boundaries can find real-world postmortems and patterns curated on daily.dev._

### Why do async/await runtimes like Tokio run out of memory under load instead of failing gracefully?

Because tokio::spawn and async task allocations are typically unbounded by default, an ingress loop keeps accepting connections and spawning tasks even when a downstream database slows down, causing in-flight tasks to queue indefinitely until the OS OOM killer terminates the process. Queues don't fix overload; they delay the crash while making it worse.

_Engineers debugging OOM crashes under traffic spikes can track this failure pattern and mitigations on daily.dev._

### Why did work-stealing schedulers cause problems for WhatsApp's Erlang BEAM virtual machine on many-core machines?

On 100+ core machines, idle threads trying to steal work from busy cores spent their CPU cycles contending over the global runq_lock that synchronizes access to a scheduler's run queue, as detailed by Robin Morisset. Stolen tasks also lose L1 and L2 cache locality, incurring 100+ nanosecond main memory fetch penalties, making generic work-stealing schedulers hurt throughput at scale.

_Developers weighing work-stealing versus thread-per-core designs can compare tradeoffs like these on daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@pdfopsdev** · 2 upvotes

> node devs hit the same wall, just later — event loop handles I/O fine until someone does JSON.stringify on a 50MB object or a sync crypto call, then the whole process stalls. worker_threads exists but almost nobody reaches for it until they've already shipped the footgun to prod.

## Similar posts on daily.dev

- [Async Programming in Rust: Understanding Futures and Tokio](https://daily.dev/posts/async-programming-in-rust-understanding-futures-and-tokio-kmsp7jgjw) · The New Stack · 1 upvotes · 0 comments
- [Async Rust: Where does the scheduler live?](https://daily.dev/posts/async-rust-where-does-the-scheduler-live--libqds5ay) · Lobsters · 23 upvotes · 1 comments
- [Building an HTTP Server on a Thread-per-Core Framework, without Async/Await](https://daily.dev/posts/building-an-http-server-on-a-thread-per-core-framework-without-async-await-2wfvnrvli) · P99 Conf · 0 upvotes · 0 comments

---

Tags: [#rust](https://daily.dev/tags/rust)

[View this post on daily.dev](https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"The Tokio/Rayon Trap and Why Async/Await Fails Concurrency","url":"https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib"},"datePublished":"2026-07-16T04:21:09.922Z","dateModified":"2026-09-13T21:18:03.745Z","description":"async/await is criticized for conflating asynchrony with concurrency, creating production pitfalls like the Tokio/Rayon boundary problem where developers must...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/36573cf306b14b8759d7270b952e3470?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/36573cf306b14b8759d7270b952e3470?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Hacker News","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Hacker News","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/hn","url":"https://daily.dev/sources/hn"},"commentCount":1,"discussionUrl":"https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":11},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":1}],"keywords":"rust","timeRequired":"PT7M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Hacker News","item":"https://daily.dev/sources/hn"},{"@type":"ListItem","position":3,"name":"The Tokio/Rayon Trap and Why Async/Await Fails Concurrency"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib","comment":[{"@type":"Comment","text":"node devs hit the same wall, just later — event loop handles I/O fine until someone does JSON.stringify on a 50MB object or a sync crypto call, then the whole process stalls. worker_threads exists but almost nobody reaches for it until they’ve already shipped the footgun to prod.","datePublished":"2026-07-16T18:06:24.395Z","url":"https://daily.dev/posts/V76C9exiB#c-d0SVBAKSB","author":{"@type":"Person","name":"PDFops","url":"https://daily.dev/pdfopsdev","image":"https://media.daily.dev/image/upload/s---8isRBKc--/f_auto/v1782922291/avatars/avatar_orjMeK8QKaaVZwGq7ScPz?_a=BAMAMicg0"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib#faq","mainEntity":[{"@type":"Question","name":"Why does mixing Tokio and Rayon cause problems in production Rust applications?","acceptedAnswer":{"@type":"Answer","text":"Mixing Tokio and Rayon requires manually partitioning code into I/O-bound and CPU-bound functions and orchestrating message-passing between the two runtimes, since Tokio's cooperative executor stalls when a task runs CPU-heavy work like parsing a large JSON payload without yielding at an await point. Engineering teams at PostHog and Meilisearch have documented the operational pain of maintaining this boundary and preventing deadlocks between the two schedulers. Anyone untangling Tokio and Rayon boundaries can find real-world postmortems and patterns curated on daily.dev."}},{"@type":"Question","name":"Why do async/await runtimes like Tokio run out of memory under load instead of failing gracefully?","acceptedAnswer":{"@type":"Answer","text":"Because tokio::spawn and async task allocations are typically unbounded by default, an ingress loop keeps accepting connections and spawning tasks even when a downstream database slows down, causing in-flight tasks to queue indefinitely until the OS OOM killer terminates the process. Queues don't fix overload; they delay the crash while making it worse. Engineers debugging OOM crashes under traffic spikes can track this failure pattern and mitigations on daily.dev."}},{"@type":"Question","name":"Why did work-stealing schedulers cause problems for WhatsApp's Erlang BEAM virtual machine on many-core machines?","acceptedAnswer":{"@type":"Answer","text":"On 100+ core machines, idle threads trying to steal work from busy cores spent their CPU cycles contending over the global runq_lock that synchronizes access to a scheduler's run queue, as detailed by Robin Morisset. Stolen tasks also lose L1 and L2 cache locality, incurring 100+ nanosecond main memory fetch penalties, making generic work-stealing schedulers hurt throughput at scale. Developers weighing work-stealing versus thread-per-core designs can compare tradeoffs like these on daily.dev."}}]}
```

