<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9" -->

---
title: Tokio Gives Progress, Not Ordering: Scheduling 1M Tasks
description: When spawning 1 million Tokio tasks during a burst of 1000 events (each with ~1000 user tokens), task polling order drifted significantly from submission...
canonical: https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Tokio Gives Progress, Not Ordering: Scheduling 1M Tasks | daily.dev
og:description: When spawning 1 million Tokio tasks during a burst of 1000 events (each with ~1000 user tokens), task polling order drifted significantly from submission...
og:url: https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9
og:image: https://api.daily.dev/og/posts/cAAmuU6J9.png
og:image:alt: Tokio Gives Progress, Not Ordering: Scheduling 1M Tasks
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.

# Tokio Gives Progress, Not Ordering: Scheduling 1M Tasks

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

## Summary

When spawning 1 million Tokio tasks during a burst of 1000 events (each with ~1000 user tokens), task polling order drifted significantly from submission order. Tokio's scheduler — with per-worker local queues of 256, a shared global queue, and work stealing — treats all tasks equally regardless of which event created them, so earlier tasks aren't guaranteed to be polled first. This caused some early-event tasks to stay alive until the end of the burst, inflating peak memory. The fix was adding a Semaphore to bound how many events could be processed concurrently, which reduced peak memory without hurting throughput. The key insight: Tokio guarantees progress and fairness only under a bounded task count, and that bound must come from the application, not the runtime.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://pranitha.dev/posts/tokio-gives-progress-not-ordering>

## Questions this post answers

### Why did my earlier-spawned Tokio tasks get polled later than tasks spawned after them?

Tokio's multi-threaded runtime does not guarantee that tasks are polled in the order they were spawned. Each worker has a local queue capped at 256 tasks, with overflow moved to a shared global queue; workers pull from their own queue first, check the global queue occasionally, and steal from idle workers. When many tasks are submitted at once, queue overflow and work stealing can delay the first poll of earlier tasks well behind later ones, even though overall progress and throughput remain unaffected.

_Anyone tuning async Rust services for memory under bursty load can find deeper scheduler breakdowns like this via daily.dev._

### How do I prevent memory spikes when spawning a large number of short-lived Tokio tasks per event?

Bound the number of concurrently processed units, such as events, using a Semaphore rather than letting task creation stay unbounded. In one case, allowing unbounded spawning of up to 1000 sub-tasks per event across bursts of 1000 events led to peak memory spikes because a few straggler tasks per event kept their parent event state alive until the whole burst finished; adding a Semaphore to cap concurrent events reduced peak memory significantly without hurting throughput.

_Developers debugging async memory blowups can track patterns like this Tokio fix through daily.dev._

### Does Tokio guarantee fair scheduling of tasks by submission order?

No, Tokio guarantees fair scheduling and progress only under a bounded number of tasks, assuming no task blocks a worker thread, but it does not guarantee that tasks are polled in submission order. The boundedness required for that fairness guarantee must come from the application itself, since Tokio has no concept of an application-level grouping like an event and will treat every task identically regardless of when or why it was created.

_Teams designing fairness guarantees on top of Tokio can compare approaches like this on daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@khauma** · 0 upvotes

> Nice write-up

**@trevorsuna** · 0 upvotes

> The key lesson lands well: the runtime can promise progress, but it can’t invent your app’s concurrency limit. A semaphore is simple, but putting that bound in the right place is what keeps 1M tasks from becoming 1M memory problems.

## Similar posts on daily.dev

- [The Tokio/Rayon Trap and Why Async/Await Fails Concurrency](https://daily.dev/posts/the-tokio-rayon-trap-and-why-async-await-fails-concurrency-v76c9exib) · Hacker News · 11 upvotes · 1 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

---

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

[View this post on daily.dev](https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9)

```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":"Tokio Gives Progress, Not Ordering: Scheduling 1M Tasks","url":"https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9"},"datePublished":"2026-07-27T23:36:21.459Z","dateModified":"2026-09-14T07:39:58.354Z","description":"When spawning 1 million Tokio tasks during a burst of 1000 events (each with ~1000 user tokens), task polling order drifted significantly from submission...","image":"https://media.daily.dev/image/upload/s--OHB84bZF--/f_auto/v1722860399/public/Placeholder%2010","thumbnailUrl":"https://media.daily.dev/image/upload/s--OHB84bZF--/f_auto/v1722860399/public/Placeholder%2010","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":2,"discussionUrl":"https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":2}],"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":"Tokio Gives Progress, Not Ordering: Scheduling 1M Tasks"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9","comment":[{"@type":"Comment","text":"Nice write-up","datePublished":"2026-07-28T14:00:25.423Z","url":"https://daily.dev/posts/cAAmuU6J9#c-MFzkkZ2RY","author":{"@type":"Person","name":"Kahlil Wallace","url":"https://daily.dev/khauma","image":"https://media.daily.dev/image/upload/s--QvppE9Ip--/f_auto/v1767378558/avatars/avatar_7eQFeSQRDx0muV3n92gxT?_a=BAMAK+ZW0"}},{"@type":"Comment","text":"The key lesson lands well: the runtime can promise progress, but it can’t invent your app’s concurrency limit. A semaphore is simple, but putting that bound in the right place is what keeps 1M tasks from becoming 1M memory problems.","datePublished":"2026-07-28T03:02:37.357Z","url":"https://daily.dev/posts/cAAmuU6J9#c-q3BTu74Fy","author":{"@type":"Person","name":"Trevor Suna","url":"https://daily.dev/trevorsuna","image":"https://media.daily.dev/image/upload/s--dZ7gXxpp--/f_auto/v1784081551/avatars/avatar_EMoP47rpuw8DNjhp6R1b6?_a=BAMAMicg0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/tokio-gives-progress-not-ordering-scheduling-1m-tasks-caamuu6j9#faq","mainEntity":[{"@type":"Question","name":"Why did my earlier-spawned Tokio tasks get polled later than tasks spawned after them?","acceptedAnswer":{"@type":"Answer","text":"Tokio's multi-threaded runtime does not guarantee that tasks are polled in the order they were spawned. Each worker has a local queue capped at 256 tasks, with overflow moved to a shared global queue; workers pull from their own queue first, check the global queue occasionally, and steal from idle workers. When many tasks are submitted at once, queue overflow and work stealing can delay the first poll of earlier tasks well behind later ones, even though overall progress and throughput remain unaffected. Anyone tuning async Rust services for memory under bursty load can find deeper scheduler breakdowns like this via daily.dev."}},{"@type":"Question","name":"How do I prevent memory spikes when spawning a large number of short-lived Tokio tasks per event?","acceptedAnswer":{"@type":"Answer","text":"Bound the number of concurrently processed units, such as events, using a Semaphore rather than letting task creation stay unbounded. In one case, allowing unbounded spawning of up to 1000 sub-tasks per event across bursts of 1000 events led to peak memory spikes because a few straggler tasks per event kept their parent event state alive until the whole burst finished; adding a Semaphore to cap concurrent events reduced peak memory significantly without hurting throughput. Developers debugging async memory blowups can track patterns like this Tokio fix through daily.dev."}},{"@type":"Question","name":"Does Tokio guarantee fair scheduling of tasks by submission order?","acceptedAnswer":{"@type":"Answer","text":"No, Tokio guarantees fair scheduling and progress only under a bounded number of tasks, assuming no task blocks a worker thread, but it does not guarantee that tasks are polled in submission order. The boundedness required for that fairness guarantee must come from the application itself, since Tokio has no concept of an application-level grouping like an event and will treat every task identically regardless of when or why it was created. Teams designing fairness guarantees on top of Tokio can compare approaches like this on daily.dev."}}]}
```

