<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tokio on</title><link>/tags/tokio/</link><description>Recent content in Tokio on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Wed, 12 Feb 2025 17:09:33 +0000</lastBuildDate><atom:link href="/tags/tokio/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 20: Production Async Architecture — Connection pools, retries, circuit breakers</title><link>/post/rust/rust-async-production-patterns/</link><pubDate>Wed, 12 Feb 2025 17:09:33 +0000</pubDate><guid>/post/rust/rust-async-production-patterns/</guid><description>&lt;p&gt;This is the lesson that ties everything together. Over the last 19 lessons, we&amp;rsquo;ve built up from mental models to executors, from channels to cancellation safety. Now it&amp;rsquo;s time to put it all into a production-grade architecture.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve run async Rust services handling tens of thousands of requests per second. The patterns in this lesson are the ones that survived contact with real traffic, real failures, and 3 AM pages. Nothing theoretical here — just battle-tested code.&lt;/p&gt;</description></item><item><title>Lesson 19: Testing Async Code — Mocking time and I/O</title><link>/post/rust/rust-async-testing/</link><pubDate>Mon, 10 Feb 2025 11:28:56 +0000</pubDate><guid>/post/rust/rust-async-testing/</guid><description>&lt;p&gt;Async code is harder to test than sync code. Not because the logic is more complex, but because time, I/O, and concurrency introduce non-determinism. A test that passes 99 times and fails on the 100th is worse than a test that always fails — at least the always-failing test tells you something.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve developed a set of patterns for testing async Rust that eliminate flakiness. The key insight: mock the things that make tests non-deterministic (time, network, randomness) and let everything else run for real.&lt;/p&gt;</description></item><item><title>Lesson 18: Tracing Async Code — Context propagation across .await</title><link>/post/rust/rust-async-tracing/</link><pubDate>Sat, 08 Feb 2025 15:43:29 +0000</pubDate><guid>/post/rust/rust-async-tracing/</guid><description>&lt;p&gt;Debugging async code with &lt;code&gt;println!&lt;/code&gt; is like debugging a highway pileup by asking each car individually what happened. You get disconnected fragments — &amp;ldquo;I was going north,&amp;rdquo; &amp;ldquo;I hit something,&amp;rdquo; &amp;ldquo;I heard a crash&amp;rdquo; — but no coherent story. With 500 concurrent tasks all writing to stdout, good luck finding which log line belongs to which request.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;tracing&lt;/code&gt; crate solves this. It gives you structured, contextual logging that follows your request across tasks, &lt;code&gt;.await&lt;/code&gt; points, and even service boundaries. I switched from &lt;code&gt;log&lt;/code&gt; to &lt;code&gt;tracing&lt;/code&gt; three years ago and haven&amp;rsquo;t looked back.&lt;/p&gt;</description></item><item><title>Lesson 17: Pin in Async Context — Why futures must be pinned</title><link>/post/rust/rust-async-pinning-revisited/</link><pubDate>Thu, 06 Feb 2025 08:27:44 +0000</pubDate><guid>/post/rust/rust-async-pinning-revisited/</guid><description>&lt;p&gt;I&amp;rsquo;ve been hand-waving around &lt;code&gt;Pin&lt;/code&gt; for 16 lessons. Every time we saw &lt;code&gt;Pin&amp;lt;&amp;amp;mut Self&amp;gt;&lt;/code&gt; in a &lt;code&gt;poll&lt;/code&gt; method, I said &amp;ldquo;don&amp;rsquo;t worry about it.&amp;rdquo; But now it&amp;rsquo;s time to worry about it, because without Pin, async Rust&amp;rsquo;s entire safety model falls apart.&lt;/p&gt;
&lt;p&gt;The good news: the mental model is simpler than it looks. The bad news: the &lt;em&gt;syntax&lt;/em&gt; is still ugly. Let&amp;rsquo;s deal with both.&lt;/p&gt;
&lt;h2 id="the-problem-pin-solves"&gt;The Problem Pin Solves&lt;/h2&gt;
&lt;p&gt;When the compiler turns your &lt;code&gt;async fn&lt;/code&gt; into a state machine, it needs to store local variables across &lt;code&gt;.await&lt;/code&gt; points. Sometimes those variables reference each other:&lt;/p&gt;</description></item><item><title>Lesson 16: How Async Executors Work Under the Hood — Demystifying the runtime</title><link>/post/rust/rust-async-executor-internals/</link><pubDate>Tue, 04 Feb 2025 13:56:18 +0000</pubDate><guid>/post/rust/rust-async-executor-internals/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every async Rust developer&amp;rsquo;s journey where the runtime stops being a black box and starts being a machine you understand. For me, it was when I built a toy executor from scratch. Suddenly, &lt;code&gt;Waker&lt;/code&gt;, &lt;code&gt;Context&lt;/code&gt;, &lt;code&gt;poll_ready&lt;/code&gt; — all of it made sense. Not as abstract concepts, but as mechanical parts.&lt;/p&gt;
&lt;p&gt;This lesson won&amp;rsquo;t make you build a production executor. But it will show you how the pieces fit together, and that understanding will change how you write and debug async code.&lt;/p&gt;</description></item><item><title>Lesson 15: Backpressure — Bounded channels and flow control</title><link>/post/rust/rust-async-backpressure/</link><pubDate>Sun, 02 Feb 2025 10:33:45 +0000</pubDate><guid>/post/rust/rust-async-backpressure/</guid><description>&lt;p&gt;The fastest way to kill a production service is to accept work faster than you can process it. I learned this the hard way when a log ingestion pipeline I built consumed 50GB of RAM and crashed because I used unbounded channels everywhere. &amp;ldquo;It works fine in testing&amp;rdquo; — yeah, testing with 100 messages, not 10 million.&lt;/p&gt;
&lt;p&gt;Backpressure is the mechanism by which a system says &amp;ldquo;slow down, I&amp;rsquo;m full.&amp;rdquo; Without it, fast producers overwhelm slow consumers, and your only flow control is the OOM killer.&lt;/p&gt;</description></item><item><title>Lesson 14: Tower — The service middleware pattern</title><link>/post/rust/rust-async-tower/</link><pubDate>Fri, 31 Jan 2025 19:18:37 +0000</pubDate><guid>/post/rust/rust-async-tower/</guid><description>&lt;p&gt;I avoided Tower for months. The trait bounds looked terrifying, the documentation assumed you already knew what you were doing, and I couldn&amp;rsquo;t figure out why I&amp;rsquo;d want it when I could just write functions. Then I needed to add logging, retries, timeouts, and rate limiting to every HTTP handler in a service with 40 endpoints.&lt;/p&gt;
&lt;p&gt;Writing those as middleware that composes? That&amp;rsquo;s Tower&amp;rsquo;s whole thing. And once it clicks, you&amp;rsquo;ll never build a service without it.&lt;/p&gt;</description></item><item><title>Lesson 13: Building HTTP Clients with reqwest — Async HTTP done right</title><link>/post/rust/rust-async-http-clients/</link><pubDate>Thu, 30 Jan 2025 16:42:09 +0000</pubDate><guid>/post/rust/rust-async-http-clients/</guid><description>&lt;p&gt;Almost every backend service I&amp;rsquo;ve built makes HTTP calls to &lt;em&gt;something&lt;/em&gt; — a third-party API, another microservice, a webhook endpoint. And almost every production HTTP bug I&amp;rsquo;ve dealt with comes from one of three things: missing timeouts, not reusing connections, or ignoring response bodies.&lt;/p&gt;
&lt;p&gt;reqwest is the HTTP client for async Rust. It&amp;rsquo;s built on hyper and Tokio, handles connection pooling, TLS, cookies, compression, and all the stuff you don&amp;rsquo;t want to think about. But you still need to use it correctly.&lt;/p&gt;</description></item><item><title>Lesson 12: Async I/O — Files, sockets, DNS</title><link>/post/rust/rust-async-io/</link><pubDate>Tue, 28 Jan 2025 08:55:13 +0000</pubDate><guid>/post/rust/rust-async-io/</guid><description>&lt;p&gt;Here&amp;rsquo;s an uncomfortable truth about async file I/O: on most operating systems, it doesn&amp;rsquo;t really exist. When you call &lt;code&gt;tokio::fs::read_to_string&lt;/code&gt;, Tokio dispatches the operation to a thread pool because Linux&amp;rsquo;s file I/O isn&amp;rsquo;t truly asynchronous (yes, there&amp;rsquo;s io_uring, but Tokio doesn&amp;rsquo;t use it by default). Network I/O, on the other hand, is genuinely async through epoll/kqueue.&lt;/p&gt;
&lt;p&gt;Understanding this distinction matters. It changes how you architect things.&lt;/p&gt;
&lt;h2 id="the-async-io-traits"&gt;The Async I/O Traits&lt;/h2&gt;
&lt;p&gt;Tokio defines two core traits that mirror their std counterparts:&lt;/p&gt;</description></item><item><title>Lesson 11: Timeouts, Deadlines, and Graceful Shutdown — Bounded operations</title><link>/post/rust/rust-async-timeouts/</link><pubDate>Sun, 26 Jan 2025 12:24:51 +0000</pubDate><guid>/post/rust/rust-async-timeouts/</guid><description>&lt;p&gt;Every production outage I&amp;rsquo;ve investigated boils down to one of two things: unbounded retries or missing timeouts. A function that &amp;ldquo;usually takes 50ms&amp;rdquo; eventually takes 30 seconds because the database is overloaded, and suddenly your entire service is frozen because every thread is waiting on that one slow call.&lt;/p&gt;
&lt;p&gt;Timeouts aren&amp;rsquo;t optional in production code. They&amp;rsquo;re as important as error handling. And graceful shutdown — cleanly stopping your service when it&amp;rsquo;s time to deploy — is what separates &amp;ldquo;my service runs in production&amp;rdquo; from &amp;ldquo;my service runs in production &lt;em&gt;well&lt;/em&gt;.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 10: Cancellation Safety — The silent footgun</title><link>/post/rust/rust-async-cancellation/</link><pubDate>Fri, 24 Jan 2025 09:41:22 +0000</pubDate><guid>/post/rust/rust-async-cancellation/</guid><description>&lt;p&gt;This is the lesson I wish someone had shoved in my face before I wrote my first &lt;code&gt;select!&lt;/code&gt; loop. I lost three days to a bug where messages were disappearing from a queue. No errors. No panics. Just&amp;hellip; gone. Turns out, &lt;code&gt;select!&lt;/code&gt; was cancelling a future that had already read the message from the channel but hadn&amp;rsquo;t finished processing it.&lt;/p&gt;
&lt;p&gt;Cancellation safety is the most under-discussed footgun in async Rust. If you use &lt;code&gt;select!&lt;/code&gt;, you need to understand this.&lt;/p&gt;</description></item><item><title>Lesson 9: Semaphores and Rate Limiting — Bounded async concurrency</title><link>/post/rust/rust-async-semaphores/</link><pubDate>Wed, 22 Jan 2025 15:08:36 +0000</pubDate><guid>/post/rust/rust-async-semaphores/</guid><description>&lt;p&gt;I once crashed a third-party API by spawning 10,000 concurrent requests from an async Rust service. The code was correct — every request completed (eventually). But the API&amp;rsquo;s rate limiter kicked in after 50 concurrent connections, and we got IP-banned for two hours.&lt;/p&gt;
&lt;p&gt;The fix was a single type: &lt;code&gt;tokio::sync::Semaphore&lt;/code&gt;. Five lines of code turned &amp;ldquo;as fast as possible&amp;rdquo; into &amp;ldquo;at most N at a time.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="whats-a-semaphore"&gt;What&amp;rsquo;s a Semaphore?&lt;/h2&gt;
&lt;p&gt;A semaphore is a counter with a maximum value. You acquire a permit before doing work, and release it when you&amp;rsquo;re done. If all permits are taken, acquiring blocks (yields) until one becomes available.&lt;/p&gt;</description></item><item><title>Lesson 8: Async Mutexes — tokio::sync::Mutex vs std</title><link>/post/rust/rust-async-mutex/</link><pubDate>Mon, 20 Jan 2025 07:15:44 +0000</pubDate><guid>/post/rust/rust-async-mutex/</guid><description>&lt;p&gt;&amp;ldquo;Should I use &lt;code&gt;tokio::sync::Mutex&lt;/code&gt; or &lt;code&gt;std::sync::Mutex&lt;/code&gt; in async code?&amp;rdquo; I&amp;rsquo;ve seen this question in every Rust Discord server, every forum, every team Slack. And the answer most people give — &amp;ldquo;always use the async one in async code&amp;rdquo; — is wrong.&lt;/p&gt;
&lt;p&gt;The real answer depends on how long you hold the lock and whether you need to &lt;code&gt;.await&lt;/code&gt; while holding it. Get this wrong and you&amp;rsquo;ll either deadlock your runtime or tank your performance.&lt;/p&gt;</description></item><item><title>Lesson 7: Async Channels — tokio::sync::mpsc</title><link>/post/rust/rust-async-channels/</link><pubDate>Sat, 18 Jan 2025 13:52:28 +0000</pubDate><guid>/post/rust/rust-async-channels/</guid><description>&lt;p&gt;The first real async service I built had a classic architecture: an HTTP handler receives a request, puts work on a queue, a background worker processes it, and the result gets sent back. In Go, this is channels all day. In async Rust, it&amp;rsquo;s &lt;em&gt;also&lt;/em&gt; channels — but you&amp;rsquo;ve got four different kinds to choose from, and picking the wrong one leads to subtle bugs.&lt;/p&gt;
&lt;p&gt;This lesson covers all four of Tokio&amp;rsquo;s channel types and when to reach for each one.&lt;/p&gt;</description></item><item><title>Lesson 6: Streams — Async iterators</title><link>/post/rust/rust-async-streams/</link><pubDate>Thu, 16 Jan 2025 10:31:07 +0000</pubDate><guid>/post/rust/rust-async-streams/</guid><description>&lt;p&gt;Regular iterators give you one value at a time, synchronously. Futures give you one value, asynchronously. Streams give you &lt;em&gt;multiple&lt;/em&gt; values, asynchronously. It&amp;rsquo;s the obvious combination, and once you start using them, you&amp;rsquo;ll wonder how you ever processed sequences of async data without them.&lt;/p&gt;
&lt;p&gt;I first needed streams when building a log aggregator. I had dozens of log sources, each producing lines at their own pace. I needed to merge them, filter them, and process them in real time. Without streams, that code was a tangled mess of channels and select loops. With streams, it was a pipeline.&lt;/p&gt;</description></item><item><title>Lesson 5: tokio::select! — Racing futures</title><link>/post/rust/rust-async-select/</link><pubDate>Tue, 14 Jan 2025 16:19:55 +0000</pubDate><guid>/post/rust/rust-async-select/</guid><description>&lt;p&gt;A couple months ago I was building a WebSocket handler that needed to do three things simultaneously: read from the socket, check a shutdown signal, and send periodic heartbeats. With &lt;code&gt;join!&lt;/code&gt;, I&amp;rsquo;d need all three to complete. But I didn&amp;rsquo;t want them all to complete — I wanted to react to whichever one happened &lt;em&gt;first&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what &lt;code&gt;select!&lt;/code&gt; does. It races multiple futures and gives you the result of the winner. The losers are dropped.&lt;/p&gt;</description></item><item><title>Lesson 4: Spawning Tasks and JoinHandles — Concurrent work units</title><link>/post/rust/rust-async-spawn-join/</link><pubDate>Sun, 12 Jan 2025 08:44:19 +0000</pubDate><guid>/post/rust/rust-async-spawn-join/</guid><description>&lt;p&gt;I remember the exact moment async Rust &amp;ldquo;clicked&amp;rdquo; for me. I was building an API aggregator that needed to call five different services. My first version awaited them sequentially — 2 seconds total. Then I spawned them as concurrent tasks — 400ms. Same work, 5x faster, and I didn&amp;rsquo;t need to think about threads, locks, or shared state.&lt;/p&gt;
&lt;p&gt;But spawning tasks isn&amp;rsquo;t free, and &lt;code&gt;JoinHandle&lt;/code&gt; has some sharp edges that nobody warned me about. This lesson covers the patterns you&amp;rsquo;ll use every day.&lt;/p&gt;</description></item><item><title>Lesson 3: Tokio — The runtime that powers async Rust</title><link>/post/rust/rust-async-tokio-intro/</link><pubDate>Fri, 10 Jan 2025 11:05:33 +0000</pubDate><guid>/post/rust/rust-async-tokio-intro/</guid><description>&lt;p&gt;Every async Rust tutorial shows you &lt;code&gt;#[tokio::main]&lt;/code&gt; in the first example and then moves on like that&amp;rsquo;s totally self-explanatory. It&amp;rsquo;s not. That macro hides a &lt;em&gt;lot&lt;/em&gt; of important decisions, and if you don&amp;rsquo;t understand what it&amp;rsquo;s doing, you&amp;rsquo;re going to make some costly mistakes.&lt;/p&gt;
&lt;p&gt;I once spent an entire day debugging a deadlock that happened because I was using the single-threaded runtime without realizing it. One &lt;code&gt;#[tokio::main(flavor = &amp;quot;current_thread&amp;quot;)]&lt;/code&gt; vs the default, and my entire application&amp;rsquo;s behavior changed. That shouldn&amp;rsquo;t surprise anyone — but it surprised me.&lt;/p&gt;</description></item><item><title>Lesson 2: The Future Trait — What .await actually does</title><link>/post/rust/rust-async-future-trait/</link><pubDate>Wed, 08 Jan 2025 14:37:42 +0000</pubDate><guid>/post/rust/rust-async-future-trait/</guid><description>&lt;p&gt;After I understood the mental model from lesson 1, my next question was obvious: what happens &lt;em&gt;mechanically&lt;/em&gt; when I write &lt;code&gt;.await&lt;/code&gt;? I could accept &amp;ldquo;it&amp;rsquo;s a state machine&amp;rdquo; as a hand-wave, but I wanted to see the gears turning.&lt;/p&gt;
&lt;p&gt;Turns out, the answer is beautifully simple once you strip away the syntax sugar. The entire async machinery in Rust boils down to one trait, two types, and a contract.&lt;/p&gt;
&lt;h2 id="the-future-trait-for-real-this-time"&gt;The Future Trait, For Real This Time&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::pin::Pin;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::task::{Context, Poll};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;trait&lt;/span&gt; Future {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Output&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;poll&lt;/span&gt;(self: &lt;span style="color:#a6e22e"&gt;Pin&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; Self&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, cx: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; Context&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&amp;#39;_&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Poll&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Self::Output&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Three things to note:&lt;/p&gt;</description></item><item><title>Lesson 1: Async Mental Model — Futures, not threads</title><link>/post/rust/rust-async-mental-model/</link><pubDate>Mon, 06 Jan 2025 09:22:14 +0000</pubDate><guid>/post/rust/rust-async-mental-model/</guid><description>&lt;p&gt;I spent three weeks writing async Rust code that compiled, ran, and produced correct results — while having absolutely no idea what was actually happening. I was copy-pasting &lt;code&gt;async fn&lt;/code&gt;, slapping &lt;code&gt;.await&lt;/code&gt; on things, and hoping for the best. Sound familiar?&lt;/p&gt;
&lt;p&gt;The problem wasn&amp;rsquo;t syntax. The problem was that I was thinking about async Rust the same way I thought about threads in Go or Java. And that mental model is &lt;em&gt;wrong&lt;/em&gt; for Rust.&lt;/p&gt;</description></item></channel></rss>