<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[workinprogress]]></title><description><![CDATA[workinprogress]]></description><link>https://workinprogress.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 28 Sep 2026 16:48:05 GMT</lastBuildDate><atom:link href="https://workinprogress.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Note #2: Coroutines, Awaitables & Tasks (Working Through Async in Python)]]></title><description><![CDATA[Types of Coroutines
I’m sorting out that there are three coroutine flavors in Python:

Native coroutines — the async def / await style.
  async def fn(x: str) -> str:
      foo = await some_fn(x)
      return foo

  Key points:

fn("bar") doesn’t sta...]]></description><link>https://workinprogress.hashnode.dev/note-2-coroutines-awaitables-and-tasks-working-through-async-in-python</link><guid isPermaLink="true">https://workinprogress.hashnode.dev/note-2-coroutines-awaitables-and-tasks-working-through-async-in-python</guid><category><![CDATA[asynchronous]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Jennet Turkkan]]></dc:creator><pubDate>Tue, 30 Sep 2025 16:45:20 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-types-of-coroutines">Types of Coroutines</h3>
<p>I’m sorting out that there are <strong>three</strong> coroutine flavors in Python:</p>
<ul>
<li><p><strong>Native coroutines</strong> — the <code>async def</code> / <code>await</code> style.</p>
<pre><code class="lang-go">  async def fn(x: str) -&gt; str:
      foo = await some_fn(x)
      <span class="hljs-keyword">return</span> foo
</code></pre>
<p>  Key points:</p>
<ul>
<li><p><code>fn("bar")</code> doesn’t start running the body — it returns a coroutine object.</p>
</li>
<li><p>The event loop is what actually <em>drives</em> execution.</p>
</li>
<li><p>Execution proceeds until the first <code>await</code>, then <em>suspends</em>.</p>
</li>
<li><p>While suspended, the loop can run something else (another coroutine).</p>
</li>
<li><p>When the awaited thing finishes, control returns and execution continues.</p>
</li>
</ul>
</li>
<li><p><strong>Classic coroutines</strong> — the “old style” with <code>.send(...)</code> and <code>yield</code>.<br />  You manually push data in, get yields out, etc. More low-level.</p>
</li>
<li><p><strong>Generator-based coroutines</strong> — you can wrap a generator and turn it into something awaitable using <code>@types.coroutine</code>.<br />  So you get something of a hybrid: generator under the hood, but usable with <code>await</code>.</p>
</li>
</ul>
<p>Also: <strong>asynchronous generators</strong> — <code>async def</code> with <code>yield</code> inside. Useful when you want to yield values over time, with awaits in between, and consume via <code>async for</code>.</p>
<hr />
<h3 id="heading-how-native-coroutines-work-step-by-step">How Native Coroutines Work (Step by Step)</h3>
<p>Let me walk through the lifecycle of:</p>
<pre><code class="lang-go">async def fn(x: str) -&gt; str:
    foo = await some_fn(x)
    <span class="hljs-keyword">return</span> foo
</code></pre>
<ol>
<li><p>Call <code>fn("bar")</code> → you get a coroutine object. Nothing inside runs yet.</p>
</li>
<li><p>You hand it off to the event loop (directly or via a Task).</p>
</li>
<li><p>Event loop starts it — runs until <code>await some_fn(x)</code>.</p>
</li>
<li><p>At <code>await some_fn(x)</code>, it calls <code>some_fn(x)</code>, which must itself return something awaitable.</p>
</li>
<li><p>The current coroutine (<code>fn("bar")</code>) suspends, yielding control back to the event loop.</p>
</li>
<li><p>The loop can pick up <em>other</em> coroutines and run them in the meantime.</p>
</li>
<li><p>When <code>some_fn("bar")</code> completes (e.g. after I/O), the loop resumes <code>fn("bar")</code> right after the <code>await</code>.</p>
</li>
<li><p>That expression yields a result (or raises), which gets assigned to <code>foo</code>.</p>
</li>
<li><p>Execution continues until the function returns (or raises).</p>
</li>
</ol>
<p>This model is what gives concurrency (but cooperatively).</p>
<hr />
<h3 id="heading-awaitables-tasks-amp-scheduling">Awaitables, Tasks &amp; Scheduling</h3>
<p>Here’s where I had to pause and think:</p>
<ul>
<li><p><code>await some_coro</code> → you wait for the result. You <strong>cannot</strong> proceed until that awaitable is done (or errors).</p>
</li>
<li><p><code>asyncio.create_task(some_coro)</code> → you wrap that coroutine into a <code>Task</code> and hand it to the loop. It will run in the background (interleaved with others).</p>
</li>
</ul>
<p>Important nuance:</p>
<ul>
<li><p>You <strong>don’t</strong> have to <code>await</code> a <code>Task</code> if you don’t care about its result.</p>
</li>
<li><p>If all you want is <em>“run this concurrently, don’t block me here”</em>, task is your friend.</p>
</li>
<li><p>If you <em>need</em> the result or must wait for it, use <code>await</code>.</p>
</li>
</ul>
<p>Because tasks are just wrappers around coroutines: when one task is suspended (on <code>await</code>), the loop can go run another.</p>
<hr />
<h3 id="heading-running-multiple-coroutines-together">Running Multiple Coroutines Together</h3>
<p>I see two main choices:</p>
<h4 id="heading-asynciogather"><code>asyncio.gather</code></h4>
<pre><code class="lang-go">results = await asyncio.gather(
    fn(<span class="hljs-string">"bar"</span>),
    fn(<span class="hljs-string">"baz"</span>),
    fn(<span class="hljs-string">"qux"</span>),
)
</code></pre>
<ul>
<li><p>It schedules all given coroutines concurrently.</p>
</li>
<li><p>You <code>await gather(...)</code> to get the list of results.</p>
</li>
<li><p>Problem: error handling is awkward. If one task fails, the others keep running unless you explicitly cancel them.</p>
</li>
</ul>
<h4 id="heading-asynciotaskgroup"><code>asyncio.TaskGroup</code></h4>
<pre><code class="lang-go">async with asyncio.TaskGroup() as tg:
    tg.create_task(fn(<span class="hljs-string">"bar"</span>))
    tg.create_task(fn(<span class="hljs-string">"baz"</span>))
    tg.create_task(fn(<span class="hljs-string">"qux"</span>))
    # when you exit the <span class="hljs-string">`async with`</span>, it waits on all, and cancels on failure
</code></pre>
<ul>
<li><p>More “structured concurrency” style.</p>
</li>
<li><p>If one fails, the rest are cancelled.</p>
</li>
<li><p>Cleaner, safer than gather for many use cases.</p>
</li>
</ul>
<hr />
<h3 id="heading-key-takeaways">Key Takeaways</h3>
<ul>
<li><p><strong>Calling an async function doesn’t run it</strong> → it just returns a coroutine object.</p>
</li>
<li><p>The event loop drives execution, stepping through until <code>await</code> points, suspending and resuming as needed.</p>
</li>
<li><p>Use <code>await</code> when you need the result.</p>
</li>
<li><p>Use <code>asyncio.create_task</code> when you want to schedule concurrent work without blocking.</p>
</li>
<li><p>For running multiple coroutines, <code>TaskGroup</code> is safer and more structured than <code>gather</code>.</p>
</li>
</ul>
<h3 id="heading-sources-amp-further-reading">Sources &amp; Further Reading</h3>
<ul>
<li><p><em>Fluent Python, Chapter 21: Asynchronous Programming</em> — the chapter gives a deep, practical dive into <code>async def</code>, <code>await</code>, async iterators, and more. <a target="_blank" href="https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/ch21.html?utm_source=chatgpt.com">O'Reilly Media</a></p>
</li>
<li><p><em>“Asyncio in Python – Full Tutorial”</em> (YouTube video) — a solid visual walkthrough of how async IO works in Python. <a target="_blank" href="https://www.youtube.com/watch?v=Qb9s3UiMSTA&amp;utm_source=chatgpt.com">YouTube</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Note #1: Buffer compaction in HTTP parsing]]></title><description><![CDATA[While working on the Boot.dev course about HTTP and TCP, I had to build a parser for the request line. That meant reading raw bytes from a connection and then figuring out where the request line ends.
To handle this, I used a fixed-size buffer. Each ...]]></description><link>https://workinprogress.hashnode.dev/note-1-buffer-compaction-in-http-parsing</link><guid isPermaLink="true">https://workinprogress.hashnode.dev/note-1-buffer-compaction-in-http-parsing</guid><category><![CDATA[http]]></category><category><![CDATA[parser]]></category><category><![CDATA[TCP]]></category><dc:creator><![CDATA[Jennet Turkkan]]></dc:creator><pubDate>Thu, 18 Sep 2025 14:16:39 GMT</pubDate><content:encoded><![CDATA[<p>While working on the <a target="_blank" href="http://Boot.dev">Boot.dev</a> course about HTTP and TCP, I had to build a parser for the request line. That meant reading raw bytes from a connection and then figuring out where the request line ends.</p>
<p>To handle this, I used a fixed-size buffer. Each time I read from the connection, new data gets appended at <code>buf[bufLen:]</code>. Then I hand the “filled” portion, <code>buf[:bufLen]</code>, to the parser.</p>
<p>For example, say the connection sends me:</p>
<pre><code class="lang-bash">GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n
</code></pre>
<p>That’s 48 bytes total. The parser only cares about the request line, so it consumes the first 26 bytes (up to the first <code>\r\n</code>). That leaves 22 bytes still in the buffer (<code>Host:</code> <a target="_blank" href="http://example.com"><code>example.com</code></a><code>\r\n\r\n</code>) waiting for the next stage.</p>
<p>After parsing, I do something called <strong>buffer compaction</strong>:</p>
<pre><code class="lang-go"><span class="hljs-built_in">copy</span>(buf, buf[readNumbers:bufferLength])
bufferLength -= readNumbers
</code></pre>
<p>This removes the 26 bytes that were already parsed and slides the 22 leftover bytes down to the start of the buffer. The buffer now always contains only the data that still needs parsing — kind of like a rolling window.</p>
<p>What I found interesting:</p>
<ul>
<li><p><strong>Reader vs Parser</strong> → The reader just delivers arbitrary byte chunks from the OS or TCP socket; it doesn’t care about HTTP. The parser is the one that understands structure and decides how many bytes to consume. That’s why the reader might give me 48 bytes, but the parser only consumes 26 and the rest belongs to the next stage.</p>
</li>
<li><p><strong>Why compaction matters</strong> → Without compaction, I’d have to track “start” and “end” indexes manually inside the buffer. Copying leftovers to the front makes the logic simple: <code>buf[:bufLen]</code> always contains valid, unparsed data.</p>
</li>
<li><p><strong>State machine design</strong> → Right now, my parser only has two states: <code>init</code> (parse the request line) and <code>done</code> (stop). That means the leftover 22 bytes (headers + body) aren’t being parsed yet. A real HTTP parser would add states like <code>ParseHeaders</code> and <code>ParseBody</code> to handle the rest.</p>
</li>
</ul>
<p>The separation between <strong>raw data handling (reader)</strong> and <strong>protocol logic (parser)</strong> really clicked for me here. Before, I never thought much about what happens <em>between</em> the TCP stream and the parsed request — now it makes sense.</p>
]]></content:encoded></item></channel></rss>