<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://harshal.sheth.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://harshal.sheth.io/" rel="alternate" type="text/html" /><updated>2026-01-04T04:37:28+00:00</updated><id>https://harshal.sheth.io/feed.xml</id><title type="html">Harshal Sheth</title><subtitle>Harshal Sheth&apos;s personal website. This site serves as a dumping ground for my projects and writing.</subtitle><entry><title type="html">Python concurrency: gevent had it right</title><link href="https://harshal.sheth.io/2025/09/12/python-async.html" rel="alternate" type="text/html" title="Python concurrency: gevent had it right" /><published>2025-09-12T00:00:00+00:00</published><updated>2025-09-12T00:00:00+00:00</updated><id>https://harshal.sheth.io/2025/09/12/python-async</id><content type="html" xml:base="https://harshal.sheth.io/2025/09/12/python-async.html"><![CDATA[<p>I recently read “<a href="https://tonybaloney.github.io/posts/why-isnt-python-async-more-popular.html">Python has had async for 10 years -- why isn’t it more popular?</a>” and the <a href="https://news.ycombinator.com/item?id=45106189">ensuing discussion</a>. To summarize the post, async has struggled because:</p>

<ol>
  <li>Network IO can be made async but file IO currently can’t.</li>
  <li>Many operations that are not GIL-blocking are event loop-blocking, meaning threads have a leg up. In fact, many async programs still end up heavily using thread pools.</li>
  <li>Conversely, it’s also extremely easy to accidentally block the event loop.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></li>
  <li>Async requires a full parallel universe of APIs and libraries and it’s tricky to maintain both.</li>
</ol>

<p>These are all valid and compelling arguments for why <code class="language-plaintext highlighter-rouge">asyncio</code> hasn’t been adopted in leaps and bounds. But if we take a step back, I’d claim there’s a deeper reason: <strong>async was a step in the wrong direction from a language design perspective</strong>.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup></p>

<p><code class="language-plaintext highlighter-rouge">async</code> was initially added to reduce the callback craziness of stuff like Twisted while being possible to retrofit into the language. It was successful with this - blocking is fundamentally easier than callbacks<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> - but caused new problems along the way.</p>

<p>After having used a variety of languages and concurrency flavors, I’ve developed the following opinions:</p>

<ol>
  <li>The <code class="language-plaintext highlighter-rouge">async</code> keyword isn’t worth it.</li>
  <li>Structured concurrency should be the core coordination mechanism.</li>
  <li>For Python, <code class="language-plaintext highlighter-rouge">gevent</code>’s approach was mostly right all along.</li>
</ol>

<h2 id="can-we-get-rid-of-async">Can we get rid of async?</h2>

<p>Every language that introduces an <code class="language-plaintext highlighter-rouge">async</code> keyword necessarily runs into the <a href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/">function coloring</a> problem, where a single use of async spreads like a virus and infects the rest of the codebase. Function coloring means that it’s difficult to adopt async incrementally. It also splits the ecosystem, forcing library developers to either pick a side or maintain parallel APIs.<sup id="fnref:sans-io" role="doc-noteref"><a href="#fn:sans-io" class="footnote" rel="footnote">4</a></sup></p>

<p>I’d argue that JavaScript is the language with the most ergonomic async design. A big part of its success is that (1) the language already had an event loop and used callbacks heavily and (2) the Promises API made it easy to bridge between callbacks and promises. In other words, they were uniquely set up to provide some mitigations for function coloring.</p>

<p>If we broaden our scope from async to overall approaches to concurrency, Go is the clear winner on ergonomics. There are no <code class="language-plaintext highlighter-rouge">async</code> or <code class="language-plaintext highlighter-rouge">await</code> keywords; when you want a function to run concurrently, write <code class="language-plaintext highlighter-rouge">go myfunc(...)</code> and the runtime will run it in a separate green thread (aka goroutine). As a developer, I don’t need to know the details of how these goroutines are scheduled or how many OS threads we have - the abstraction allows me to ignore these details.</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
    <span class="s">"fmt"</span>
<span class="p">)</span>

<span class="k">func</span> <span class="n">myfunc</span><span class="p">(</span><span class="n">s</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">go</span> <span class="n">myfunc</span><span class="p">(</span><span class="s">"runs in a goroutine!"</span><span class="p">)</span>
    <span class="c">// ... other code</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Go had two critical insights</strong>:</p>

<ol>
  <li>If the runtime can ensure that a function yields back to the scheduler whenever it does something blocking, good things happen. Specifically, you don’t need the <code class="language-plaintext highlighter-rouge">async</code> keyword at all and hence sidestep the function coloring problem entirely. Second, it becomes impossible to block the event loop.<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">5</a></sup></li>
  <li>Concurrency should be a call-site choice, not an attribute of the called function.<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">6</a></sup></li>
</ol>

<p>Of course Go had the flexibility of being a newer language that could bake these ideas into the runtime. Everyone else had to deal with backwards compatibility concerns.</p>

<h3 id="aside-but-explicit-yield-points-are-valuable">Aside: “but explicit yield points are valuable”</h3>

<p>One of the other arguments in favor of async is that the program will only switch to other tasks at specific yield points, and therefore is easier to reason about than threads or green threads. “<a href="https://glyph.twistedmatrix.com/2014/02/unyielding.html">Unyielding</a>,” written by the founder of Twisted, makes this argument most coherently.</p>

<p>I disagree with that argument: while it’s true that concurrency is hard to get right, I don’t think explicit yield points make it that much easier. In all but the simplest scenarios, you still need shared data structures and locks. Armin Ronacher elaborates on this idea in “<a href="https://lucumr.pocoo.org/2024/11/18/threads-beat-async-await/">Playground Wisdom: Threads Beat Async/Await</a>.”</p>

<h2 id="making-concurrency-easier">Making concurrency easier</h2>

<p>The above Go code sample actually has a bug - depending on what goes in that “other code”, the print statement may or may not be executed. More broadly, coordination and correctness across goroutines remains cumbersome in Go despite the existence of channels, WaitGroups, and context.</p>

<p>Unlike Go, Python has stuff like exceptions and cancellation semantics, and these make full-featured structured concurrency possible.<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">7</a></sup> The core idea of structured concurrency is pretty <a href="https://gavinhoward.com/2019/12/structured-concurrency-definition/">simple</a>: require that concurrent tasks can’t outlive their parent scope.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">anyio</span>
<span class="kn">import</span> <span class="nn">httpx</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">worker</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">httpx</span><span class="p">.</span><span class="n">AsyncClient</span><span class="p">()</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
        <span class="n">resp</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> got </span><span class="si">{</span><span class="n">resp</span><span class="p">.</span><span class="n">status_code</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">main_with_structure</span><span class="p">():</span>
    <span class="k">async</span> <span class="k">with</span> <span class="n">anyio</span><span class="p">.</span><span class="n">create_task_group</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
        <span class="n">tg</span><span class="p">.</span><span class="n">start_soon</span><span class="p">(</span><span class="n">worker</span><span class="p">,</span> <span class="s">"worker1"</span><span class="p">,</span> <span class="s">"https://a.com"</span><span class="p">)</span>
        <span class="n">tg</span><span class="p">.</span><span class="n">start_soon</span><span class="p">(</span><span class="n">worker</span><span class="p">,</span> <span class="s">"worker2"</span><span class="p">,</span> <span class="s">"https://b.com"</span><span class="p">)</span>
        <span class="c1"># When exiting the with block, we automatically block until
</span>        <span class="c1"># all subtasks complete.
</span>        <span class="c1"># If either worker crashes, the whole group is automatically canceled.
</span>        <span class="c1"># You'll never leak/orphan a task.
</span>
<span class="n">anyio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main_with_structure</span><span class="p">)</span>
</code></pre></div></div>

<p>This has other benefits as well. It makes resource cleanup, error handling, cancellation, and control-C handling just work. Stack traces are significantly easier to understand because the call stack is still somewhat sensible. I think that reasoning about structured concurrency feels more familiar to developers without a concurrency background. If you’d like to learn more, I’d highly recommend the Trio creator’s “<a href="https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/">Notes on structured concurrency, or: Go statement considered harmful</a>.”</p>

<p>In my mind, <strong>structured concurrency is clearly the future</strong>. In Python, Trio and AnyIO have proven that the idea works. It’s important to call out that it’s applicable for all forms of concurrency - async, green threads, threads, and even multiprocessing.</p>

<h2 id="making-python-nicer">Making Python nicer</h2>

<p>As it turns out, the Python gevent library is built on basically the same insights as Go. It uses green threads for concurrency<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote" rel="footnote">8</a></sup> and makes it extremely easy to spawn off tasks. Since it’s too late to bake automatic yielding into the Python runtime, gevent relies on monkeypatching to make the standard library cooperative.<sup id="fnref:8" role="doc-noteref"><a href="#fn:8" class="footnote" rel="footnote">9</a></sup> The end result is that writing gevent code feels pretty similar to writing Go.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">gevent</span>
<span class="kn">from</span> <span class="nn">gevent</span> <span class="kn">import</span> <span class="n">monkey</span>
<span class="n">monkey</span><span class="p">.</span><span class="n">patch_all</span><span class="p">()</span>

<span class="kn">import</span> <span class="nn">requests</span>

<span class="k">def</span> <span class="nf">worker</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
    <span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>  <span class="c1"># automatically yields when blocked
</span>    <span class="c1"># If this was async, requests would've blocked the event loop.
</span>    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> got </span><span class="si">{</span><span class="n">resp</span><span class="p">.</span><span class="n">status_code</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">g1</span> <span class="o">=</span> <span class="n">gevent</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="n">worker</span><span class="p">,</span> <span class="s">"worker1"</span><span class="p">,</span> <span class="s">"https://a.com"</span><span class="p">)</span>
    <span class="n">g2</span> <span class="o">=</span> <span class="n">gevent</span><span class="p">.</span><span class="n">spawn</span><span class="p">(</span><span class="n">worker</span><span class="p">,</span> <span class="s">"worker2"</span><span class="p">,</span> <span class="s">"https://b.com"</span><span class="p">)</span>

    <span class="n">gevent</span><span class="p">.</span><span class="n">joinall</span><span class="p">([</span><span class="n">g1</span><span class="p">,</span> <span class="n">g2</span><span class="p">])</span>  <span class="c1"># wait for both to finish
</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div></div>

<p>While gevent is already quite solid,<sup id="fnref:9" role="doc-noteref"><a href="#fn:9" class="footnote" rel="footnote">10</a></sup> there are two things we’d need to build to make it a fully viable replacement for async and the asyncio ecosystem.<sup id="fnref:10" role="doc-noteref"><a href="#fn:10" class="footnote" rel="footnote">11</a></sup></p>

<ol>
  <li>A structured concurrency layer built on top of gevent. It would be to gevent as AnyIO is to asyncio. Type checking is also important here, and we can draw inspiration from the asyncer library.</li>
  <li>A shim layer that brings async programs into the fold. It should be possible to build an asyncio event loop implementation that defers to gevent for all of its actual scheduling and execution.</li>
</ol>

<p>Here’s what an amazing end state could look like:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">g</span><span class="p">.</span><span class="n">init</span><span class="p">()</span>  <span class="c1"># unfortunately we'll still need this
</span><span class="kn">import</span> <span class="nn">requests</span>

<span class="k">def</span> <span class="nf">worker</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
    <span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>  <span class="c1"># automatically yields when blocked
</span>    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s"> got </span><span class="si">{</span><span class="n">resp</span><span class="p">.</span><span class="n">status_code</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="k">async</span> <span class="k">def</span> <span class="nf">async_worker</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span> <span class="p">...</span>  <span class="c1"># omitted
</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">with</span> <span class="n">g</span><span class="p">.</span><span class="n">create_task_group</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>  <span class="c1"># structured concurrency built in
</span>        <span class="n">tg</span><span class="p">.</span><span class="n">start_soon</span><span class="p">(</span><span class="n">worker</span><span class="p">)(</span><span class="s">"worker1"</span><span class="p">,</span> <span class="s">"https://a.com"</span><span class="p">)</span>
        <span class="n">tg</span><span class="p">.</span><span class="n">start_soon</span><span class="p">(</span><span class="n">worker</span><span class="p">)(</span><span class="s">"worker2"</span><span class="p">,</span> <span class="s">"https://b.com"</span><span class="p">)</span>

        <span class="c1"># Seamlessly works with async
</span>        <span class="n">tg</span><span class="p">.</span><span class="n">start_soon</span><span class="p">(</span><span class="n">async_worker</span><span class="p">)(</span><span class="s">"worker3"</span><span class="p">,</span> <span class="s">"https://c.com"</span><span class="p">)</span>

    <span class="k">print</span><span class="p">(</span><span class="s">"All workers finished cleanly"</span><span class="p">)</span>

<span class="n">main</span><span class="p">()</span>
</code></pre></div></div>

<p>It also introduces some super interesting possibilities for future work. Most exciting to me is the possibility of leveraging <a href="https://docs.python.org/3/howto/free-threading-python.html">free threading</a> (aka no-GIL Python) by turning the gevent scheduler into a Go-style scheduler that multiplexes green threads (aka greenlets) onto multiple OS threads. While making most Python code totally thread-safe is tricky, there’s only a small jump from code that’s safe when run as a green thread to safe across non-GILed threads.</p>

<p>In case it wasn’t clear: I remain excited about the future of Python. I get the circumstances and reasoning that led to adding async, and am grateful that the flexibility it afforded enabled the prototyping of stuff like Curio and Trio. I hope Python leans more heavily towards green threads moving forward and that future programming languages make concurrency and coordination more of a first-class citizen.</p>

<p><em>Thanks to John Kim, Kevin Hu, and Shihao Cao for reviewing drafts of this post.</em></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Despite knowing all the pitfalls, I get caught by this occasionally. For instance, at DataHub my usage of FastMCP with synchronous tools caused <a href="https://github.com/jlowin/fastmcp/issues/864#issuecomment-3103990624">issues</a> that we were only able to detect once we hit some level of scale. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>There’s also many issues with the asyncio library implementation, but I’m not going to focus on those here. I’d recommend “<a href="https://lucumr.pocoo.org/2016/10/30/i-dont-understand-asyncio/">I don’t understand Python’s Asyncio</a>” or the more recent “<a href="https://sailor.li/asyncio">asyncio: a library with too many sharp corners</a>.” <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>So good that durable execution platforms like Temporal let you block in even more places. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:sans-io" role="doc-endnote">
      <p>Stuff like <a href="https://sans-io.readthedocs.io/">Sans-I/O</a> is billed as a solution, but I think it’s just emblematic of the problems <code class="language-plaintext highlighter-rouge">async</code> causes. <a href="#fnref:sans-io" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Ever since Go added support for non-cooperative preemption, you can’t block the event loop for too long. That’s effectively what you want. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>Threading actually had this right from the start, but we threw away that insight with async. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p>SourceGraph’s <a href="https://github.com/sourcegraph/conc">conc</a> library makes part of structured concurrency possible, but cancellation / timeouts / control-C handling are still pretty limited. From what I can see, those really do require exceptions. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7" role="doc-endnote">
      <p>There have been many proposals over the years to bring something like stackless/greenlets/virtual threads to Python. For instance, “<a href="https://lucumr.pocoo.org/2025/7/26/virtual-threads/">From Async/Await to Virtual Threads</a>” has similar ideas but doesn’t quite make the connection of gevent + greenlets being a presently viable vehicle for it. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8" role="doc-endnote">
      <p>A common objection is “monkey-patching is brittle”. The gevent library has had years to iterate on its approach to monkeypatching. The more popular the library becomes, the more the rest of the ecosystem leans in - so at scale this should become a moot point. <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9" role="doc-endnote">
      <p>This begs the question: gevent has been around since 2009, so why isn’t it already more popular? It is already moderately popular: currently ~31M downloads/month, but still a far cry from httpx’s 266M/month or FastAPI’s 128M/month (data via <a href="https://pypistats.org/">PyPI Stats</a>). My hypothesis is that (1) the monkeypatching was worse back then and still sounds scary, (2) gevent didn’t quite have the “batteries included” experience of Twisted/Tornado, and so (3) it never developed a broad ecosystem or community. <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10" role="doc-endnote">
      <p>I’ve actually begun prototyping something like this, but haven’t gotten too far yet. Let me know if you’re working on something similar :) <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[I recently read “Python has had async for 10 years -- why isn’t it more popular?” and the ensuing discussion. To summarize the post, async has struggled because:]]></summary></entry><entry><title type="html">How GitHub Actions won CI</title><link href="https://harshal.sheth.io/2024/05/13/github-actions.html" rel="alternate" type="text/html" title="How GitHub Actions won CI" /><published>2024-05-13T00:00:00+00:00</published><updated>2024-05-13T00:00:00+00:00</updated><id>https://harshal.sheth.io/2024/05/13/github-actions</id><content type="html" xml:base="https://harshal.sheth.io/2024/05/13/github-actions.html"><![CDATA[<p>Since GitHub Actions CI/CD became <a href="https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/">generally available</a> at the end of 2019, it’s dominated its competition.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>

<p>A few years ago, there used to be a thriving ecosystem of CI providers, like Travis CI and CircleCI, with healthy competition between them. Nowadays, every repo I come across uses GitHub Actions instead.</p>

<p>How did GitHub corner CI so quickly? By bundling Actions with existing software and leveraging their community.</p>

<h2 id="bundling">Bundling</h2>

<p>GitHub is owned by Microsoft, and bundling software is a <a href="https://www.youtube.com/watch?v=IF0GL2xEzIc">classic part</a> of the Microsoft playbook. In this case, it’s a lot easier to use one provider for both source control and CI, especially when all GitHub paid plans come bundled with free build minutes.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> GitHub Actions is automatically available on all repos, whereas other CI tools must be installed through the marketplace. For a product that, at its core, resells commodity compute at a massive markup,<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> the reduction in friction probably has an outsized impact on usage. While they once said “<a href="https://github.blog/2017-11-07-github-welcomes-all-ci-tools/">GitHub welcomes all CI tools</a>”, it seems some are more welcome than others.</p>

<h2 id="reusable-ci">Reusable CI</h2>

<p>There’s a relatively short list of ultra-common building blocks people do in CI, like installing dependencies and running linters and tests. However, GitHub also recognized that there’s a <a href="https://about.codecov.io/blog/discovering-the-most-popular-and-most-used-github-actions/">long tail</a> of functions developers want to reuse, but GitHub couldn’t reasonably build out themselves, like generating coverage reports, deploying to various cloud providers, publishing releases, and managing caches for various language/frameworks.</p>

<p>GitHub Actions enabled developers to build and reuse these building blocks, and made it easy to publish them open source for others to use as well. GitHub, which already had a huge community of open source-minded developers, was able to bootstrap a <a href="https://github.com/marketplace?type=actions">rich ecosystem</a> of custom actions fairly quickly. This ecosystem, in turn, makes GitHub Actions easier to use than any other CI tool, because the mundane parts are already done for you. Doing CI with GitHub Actions is as simple as pulling in dependencies when writing software.</p>

<h2 id="a-local-optimum">A local optimum</h2>

<p>While Travis CI and other providers make you use external scripts for reusability, GitHub Actions went one level higher, promoting these composable actions for reusability. With Travis CI,<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup> complex CI logic would be embedded in your shell scripts, whereas with Actions it’s written in a JavaScript-like language embedded within the YAML configs. From the standpoint of technical purity, I think this is the wrong take. I’d much rather write shell scripts that I can run and test locally, and minimize the difference between local development and CI. Nonetheless, I find myself increasingly building complex workflows filled with community-built actions, since it genuinely is a better quickstart experience than building shell scripts someone else has built before. That’s the power of open source, and yet GitHub Actions’s take on it feels like a local optimum.</p>

<p>There’s different takes on how to escape this local optimum - a better build system that interfaces nicely<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup> with CI (e.g. <a href="https://dagger.io">Dagger</a>, <a href="https://earthly.dev/">Earthly</a>, <a href="https://bazel.build/">Bazel</a>) is the most promising so far.<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">6</a></sup> Unfortunately, these tend to be all-or-nothing tools, so unless we scrap our existing build systems and migrate, we’re stuck writing ugly GitHub Actions pseudo-JS YAML spaghetti with no types or unit tests.<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote" rel="footnote">7</a></sup> The blog post <a href="https://blog.yossarian.net/2023/09/22/GitHub-Actions-could-be-so-much-better">“GitHub Actions could be so much better”</a> offers an even more poignant critique of the user experience, covering the difficult local development experience, lacking debugging experience, and security footguns.</p>

<h2 id="open-source-vendor-lock-in">Open source vendor lock-in</h2>

<p>The shift to actions and YAML instead of CI shell scripts serves a second, strategic purpose: increasing vendor lock-in. This is where the technical purity of providers like Travis CI hurt their long-term prospects by decreasing stickiness. While it’s easy to wholesale switch to GitHub Actions and then incrementally adopt actions, it’s very difficult to incrementally switch away when your entire CI process is intertwined with bespoke actions. It’s ironic - the code for these actions is usually community-built and open-source, and yet you get locked into GitHub Actions anyways.</p>

<h2 id="wrap-up">Wrap-up</h2>

<p>The GitHub Actions product is a collection of frustrating technical decisions (limited local development, print-oriented debuggability, security footguns, etc) and one compelling one: reusable actions.<sup id="fnref:8" role="doc-noteref"><a href="#fn:8" class="footnote" rel="footnote">8</a></sup> This technical decision meshed extremely well with GitHub’s already dominant position and enabled them to leverage their community to build a formidable product moat. Coupled with their ability to bundle a generous free tier, it’s no surprise that GitHub Actions became the <a href="https://decan.lexpage.net/files/SANER-2022a.pdf">dominant CI provider</a> on GitHub in just 18 months after launching.</p>

<!-- _Thanks to Mohak Jain for his feedback on early drafts of this article._ -->

<h2 id="notes">Notes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Determining CI provider market share turned out to be quite the rabbit hole. In 2017, GitHub <a href="https://github.blog/2017-11-07-github-welcomes-all-ci-tools/">published a chart</a> showing Travis CI at 50% market share, and CircleCI in second at ~25%, and Jenkins in third. This one was weighted by the CI context info on commits, and the numbers sound pretty reasonable to me. A <a href="https://decan.lexpage.net/files/SANER-2022a.pdf">paper</a> from 2022 that surveyed npm packages with GitHub repos found GitHub Actions at 51.7%, followed by Travis at 42.5% and CircleCI at 10.2%. A <a href="https://www.jetbrains.com/lp/devecosystem-2023/team-tools/#ci_tools">survey by JetBrains</a> in 2023 shows Jenkins in the lead at 54%, with GitHub Actions and GitLab CI trailing closely at 51% each and CircleCI at 11% and Travis CI at 9%. The results from 2022 and 2023 reports differ significantly. I suspect the reason these numbers vary so wildly is because they’re using different denominators - one used npm-associated GitHub repos and the other surveyed individuals. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>It also helped that Travis CI <a href="https://www.travis-ci.com/blog/2020-11-02-travis-ci-new-billing/">gutted</a> their free tier during that period. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Their markup is probably around 2-5x over their underlying infrastructure cost, evidenced by the <a href="https://twitter.com/jarredsumner/status/1759597721877647644">cottage industry</a> of companies that offer hosted runners for massive discounts. AWS only wishes they could get those kinds of margins. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>I’m picking on Travis CI here since they were the biggest, but this applies to most of the CI providers I’m familiar with. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>Build systems and CI <a href="https://gregoryszorc.com/blog/2021/04/07/modern-ci-is-too-complex-and-misdirected/">aren’t so different</a> and probably would benefit from some unification. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p>The other option is to outsource CD processes to <a href="https://www.swyx.io/netlify-git-centric">Git-centric</a> hosting providers like Vercel or Netlify. While this doesn’t replace CI completely, it can take away a decent chunk of the complexity. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7" role="doc-endnote">
      <p>Yes, I know about <a href="https://github.com/nektos/act">act</a> and <a href="https://github.com/rhysd/actionlint">actionlint</a> and have used them both. While better than nothing, the local development story with GitHub Actions is still nowhere near seamless. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8" role="doc-endnote">
      <p>An interesting comparison to think about: we know that GitHub Actions and Azure Pipelines <a href="https://twitter.com/natfriedman/status/1159526215658561536">share infra</a> under the hood, and their yaml formats certainly look pretty similar. The uptick in usage of Azure DevOps has been pretty small relative to that of GitHub Actions, and that difference must be explainable by the decisions GitHub made differently. <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Since GitHub Actions CI/CD became generally available at the end of 2019, it’s dominated its competition.1 Determining CI provider market share turned out to be quite the rabbit hole. In 2017, GitHub published a chart showing Travis CI at 50% market share, and CircleCI in second at ~25%, and Jenkins in third. This one was weighted by the CI context info on commits, and the numbers sound pretty reasonable to me. A paper from 2022 that surveyed npm packages with GitHub repos found GitHub Actions at 51.7%, followed by Travis at 42.5% and CircleCI at 10.2%. A survey by JetBrains in 2023 shows Jenkins in the lead at 54%, with GitHub Actions and GitLab CI trailing closely at 51% each and CircleCI at 11% and Travis CI at 9%. The results from 2022 and 2023 reports differ significantly. I suspect the reason these numbers vary so wildly is because they’re using different denominators - one used npm-associated GitHub repos and the other surveyed individuals. &#8617;]]></summary></entry><entry><title type="html">Pay attention to WebAssembly</title><link href="https://harshal.sheth.io/2022/01/31/webassembly.html" rel="alternate" type="text/html" title="Pay attention to WebAssembly" /><published>2022-01-31T00:00:00+00:00</published><updated>2022-01-31T00:00:00+00:00</updated><id>https://harshal.sheth.io/2022/01/31/webassembly</id><content type="html" xml:base="https://harshal.sheth.io/2022/01/31/webassembly.html"><![CDATA[<p><em><a href="https://postd.cc/pay-attention-to-web-assembly/">Read in Japanese</a> thanks to the folks at POSTD.</em></p>

<p>WebAssembly is at an inflection point. Over the next few years, I expect to see increased adoption of WebAssembly across the tech sphere, from containerization to plugin systems to serverless computing platforms. The following is a discussion of what WebAssembly is, what makes it a relevant technology, and where it’s being used today. I’ll also describe some potentially high-impact applications and make some predictions about its future.</p>

<h2 id="what-is-webassembly">What is WebAssembly?</h2>

<p>WebAssembly (abbreviated Wasm) is an intermediate layer between various programming languages and many different execution environments. You can take code written in over 30 different languages and compile it into a .wasm file, and then can execute that file in the browser, on a server, or even on a car.</p>

<p>The name “WebAssembly” is misleading. While it was initially designed to make code run fast on the web, it now can run in a variety of environments outside of the browser as well. Moreover, WebAssembly is not assembly but rather a slightly higher-level bytecode.</p>

<p>Plenty of ink has been spilled on describing WebAssembly and explaining its history, so I’ll simply refer to some good primers here:</p>

<ul>
  <li><a href="https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/">A cartoon intro to WebAssembly - Mozilla Hacks - the Web developer blog</a></li>
  <li><a href="https://nickymeuleman.netlify.app/blog/webassembly">WebAssembly. Scary name, exciting applications. | Nicky blogs</a></li>
  <li><a href="https://desiatov.com/why-webassembly/">How WebAssembly changes software distribution | Max Desiatov</a></li>
</ul>

<h2 id="where-does-webassembly-excel">Where does WebAssembly excel?</h2>

<p>WebAssembly excels because of the following five characteristics:</p>

<ul>
  <li><strong>Portable</strong>: The binary format for Wasm bytecode is standardized, meaning that any runtime capable of executing Wasm will be able to run any Wasm code.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> This is similar to Java’s promise of “write once, run anywhere”. In the browser, <a href="https://caniuse.com/wasm">95% of users’ browsers</a> can execute WebAssembly, and the remaining gap can be bridged using a wasm2js compiler. For servers, there are runtimes like <a href="https://github.com/bytecodealliance/wasmtime">Wasmtime</a> and <a href="https://github.com/wasmerio/wasmer">Wasmer</a>. Even resource-constrained IoT devices can join the fun using <a href="https://github.com/bytecodealliance/wasm-micro-runtime">WAMR</a>.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup></li>
  <li><strong>Universal</strong>: Many languages can compile into Wasm. This support goes beyond systems languages like C, C++, and Rust to include garbage-collected, high-level languages like Go, Python, and Ruby.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> A full list of languages that compile to Wasm can be found <a href="https://github.com/appcypher/awesome-wasm-langs">here</a>.</li>
  <li><strong>“Near-Native Performance”</strong>: Wasm is often <a href="https://developer.mozilla.org/en-US/docs/WebAssembly">described</a> as having “near-native performance”. What this actually means is that WebAssembly is almost always faster than JavaScript, especially for compute-intensive workloads, and averages between 1.45 and 1.55 times slower than <a href="https://www.usenix.org/conference/atc19/presentation/jangda">native code</a>, but results do <a href="https://00f.net/2021/02/22/webassembly-runtimes-benchmarks/">vary by runtime</a>.</li>
  <li><strong>Fast Startup Time</strong>: The cold start time of Wasm is important enough that it warrants a category of its own. On the server, it can achieve 10-100x <a href="https://repositum.tuwien.at/bitstream/20.500.12708/17598/1/Gackstatter%20Philipp%20-%202021%20-%20A%20WebAssembly%20Container%20Runtime%20for%20Serverless%20Edge...pdf">faster cold starts</a> than Docker containers because it does not need to create a new OS process for every container. In the browser, decoding Wasm and translating it to machine code is faster than parsing, interpreting, and optimizing JavaScript, and so Wasm code can begin executing at peak performance more quickly than JavaScript can.<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup></li>
  <li><strong>Secure</strong>: WebAssembly was designed with the web in mind and so security was a priority. Code running in a Wasm runtime is memory sandboxed and capability constrained, meaning that it is restricted to doing what it is explicitly allowed to do.<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup> While sandboxed, Wasm code can still be granted access to the underlying system, including system-level interfaces and hardware features.</li>
</ul>

<h2 id="where-is-webassembly-useful">Where is WebAssembly useful?</h2>

<h3 id="speeding-up-javascript">Speeding up JavaScript</h3>

<p>The initial motivation behind Wasm and its precursor asm.js was to speed up client-side code on the web, and there are many examples of Wasm excelling in this arena:</p>

<ul>
  <li>The core of the Figma design tool, for example, is written in C++ and then compiled to WebAssembly. They found major <a href="https://www.figma.com/blog/building-a-professional-design-tool-on-the-web/">performance and usability wins</a> by writing in C++, while <a href="https://www.figma.com/blog/webassembly-cut-figmas-load-time-by-3x/">compiling to WebAssembly</a> cut load times by 3x and dramatically reduced download sizes.</li>
  <li>The password manager 1Password saw up to <a href="https://blog.1password.com/1password-x-may-2019-update/">13-39x speedups</a> on form-heavy sites when switching to Wasm. Wasm performance is also <a href="https://developers.google.com/web/updates/2019/02/hotpath-with-wasm">more consistent</a> than JavaScript, which is important for latency-sensitive applications.<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">6</a></sup></li>
</ul>

<h3 id="programming-language-interoperability">Programming Language Interoperability</h3>

<p>WebAssembly lets us more easily cross the boundaries between programming languages. Libraries and frameworks are usually written in a single language, which makes it difficult to make use of that code from other languages without a full rewrite. With WebAssembly, we can more easily execute code written in other languages. <strong>This enables us to reuse code rather than reinventing the wheel.</strong></p>

<p>Right now, this is mainly used to port applications to the web. Here’s some examples:</p>

<ul>
  <li>Figma makes use of a low-level C++ library called Skia for some graphics algorithms rather than building their own or porting them to JavaScript.<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote" rel="footnote">7</a></sup></li>
  <li>My favorite chess server, lichess.org, runs the world-class Stockfish chess engine in users’ browsers, saving them the computational burden of running it server-side.</li>
  <li><a href="https://medium.com/google-earth/google-earth-comes-to-more-browsers-thanks-to-webassembly-1877d95810d6">Google Earth</a> and <a href="https://web.dev/ps-on-the-web/">Adobe Photoshop</a> ported their C++ codebases to the web using Wasm.</li>
</ul>

<p>Porting applications to the web is the easiest place to start, and we’ll likely see that <a href="https://paulbutler.org/2020/the-webassembly-app-gap/">trend continue</a>. However, Wasm’s interoperability is not limited to the browser. It’s also been used to make code work cross-platform and cross-device:</p>

<ul>
  <li>The <a href="https://platform.uno/">Uno Platform</a> is a UI platform that lets you write a single application and have it run across Windows, macOS, iOS, Android, Linux, and browsers. It seems to be fairly Windows-centric, as applications are written in C# and XAML, and so many of the use cases are based around reducing the effort required to port legacy applications to new platforms.</li>
  <li><a href="https://www.amazon.science/blog/how-prime-video-updates-its-app-for-more-than-8-000-device-types">Amazon Prime</a>, <a href="https://medium.com/disney-streaming/introducing-the-disney-application-development-kit-adk-ad85ca139073">Disney+</a>, and the <a href="https://www.youtube.com/watch?v=28paRXqI-Gk">BBC</a> all use WebAssembly in their video platforms. For example, Amazon Prime uses it to ship new features to a huge variety of device types while maintaining acceptable performance.</li>
</ul>

<p>Beyond application portability, WebAssembly can also serve as a cross-language bridge on the server-side. Unfortunately we haven’t seen too much of this yet, since the interfaces used to communicate with the operating system (the <a href="https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/">Web Assembly System Interface</a>, abbreviated WASI) and work across language boundaries (the <a href="https://github.com/WebAssembly/component-model">Wasm Component Model</a>) are still in development and have not yet reached the requisite maturity level.</p>

<h3 id="plugin-systems">Plugin Systems</h3>

<p>When most applications reach a certain level of maturity, they need to allow for extensibility by end users. Historically applications have reached for copious config systems or built complex DSLs, but these invariably turn out to be exceedingly painful to manage or force developers to work with unfamiliar languages.</p>

<p>Let’s consider an example: configuring request filtering rules in a system like NGINX. In order to do so, a sysadmin must declaratively implement their desired logic in a custom configuration language that they’re unfamiliar with. They’re beholden to the types of matching and filtering operators that the NGINX designers anticipated, which often severely limits their ability to implement the behavior they want. If anything goes wrong, debugging can be frustrating because of the lack of available tooling.</p>

<p>Some newer applications have opted for a different approach: provide a standard set of interfaces and embed a Wasm runtime, and let end users provide Wasm binaries that implement the needed custom logic. This yields a much more flexible and familiar interface for end users: they can implement arbitrarily complex business logic and can do so in whatever language they choose. This was not possible with other languages because of security concerns, but Wasm makes it feasible because the runtime can sandbox the user-provided code.</p>

<p>A few examples of where this is used today:</p>

<ul>
  <li>The Envoy proxy, originally developed by Lyft and now used across the industry, allows extensions to be <a href="https://github.com/proxy-wasm/spec/blob/master/docs/WebAssembly-in-Envoy.md">built with Wasm</a> and loaded dynamically at runtime. The Istio service mesh, which is built on top of Envoy, has followed suit.</li>
  <li>Redpanda, a Kafka alternative, lets users write <a href="https://vectorized.io/blog/wasm-architecture/">in-stream custom data transformations</a> using Wasm.</li>
  <li>The Open Policy Agent allows for policies to be <a href="https://www.openpolicyagent.org/docs/latest/wasm/">defined using Wasm</a>.</li>
  <li>The Minecraft server <a href="https://github.com/feather-rs/feather">Feather</a> uses WebAssembly to run plugins in a sandbox.</li>
</ul>

<h3 id="embedded-sandboxing">Embedded Sandboxing</h3>

<p>The idea of embedding WebAssembly within other applications is useful beyond plugin systems. In fact, it can be used to sandbox entire third-party libraries or to construct layers of security for first-party code.</p>

<p>Firefox is <a href="https://hacks.mozilla.org/2021/12/webassembly-and-back-again-fine-grained-sandboxing-in-firefox-95/">leading the way</a> in this area by protecting itself against bugs in third-party libraries, like the ones they use for spell checking or image decoding. In conjunction with a tool called RLBox, which provides a <a href="https://hacks.mozilla.org/2020/02/securing-firefox-with-webassembly/">tainting layer</a>, they can protect against vulnerabilities in those libraries without resorting to heavy-handed process isolation. For Firefox, they don’t even ship Wasm binaries in their final release; the process of compiling to Wasm and then transpiling to another language, coupled with RLBox, provides the security they need.</p>

<p>This approach might prevent some serious vulnerabilities from being exploited. Since attackers usually chain multiple vulnerabilities together, such intermediate security layers will probably be invaluable moving forwards.</p>

<h3 id="containerization">Containerization</h3>

<p>In an oft-cited <a href="https://twitter.com/solomonstre/status/1111004913222324225?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1111004913222324225%7Ctwgr%5E%7Ctwcon%5Es1_&amp;ref_url=https%3A%2F%2Fnickymeuleman.netlify.app%2Fblog%2Fwebassemblysecurity">tweet</a>, Docker founder Solomon Hykes underscored the importance of WebAssembly:</p>

<blockquote>
  <p>If WASM+WASI existed in 2008, we wouldn’t have needed to created Docker. That’s how important it is. Webassembly on the server is the future of computing.</p>
</blockquote>

<p>There’s <a href="https://kubesphere.io/blogs/will-cloud-native-webassembly-replace-docker_/">good reason</a> to believe that Wasm represents the future of containerization. <strong>Compared to Docker, it has 10-100x faster cold start times, has a smaller footprint, and uses a better-constrained capability-based security model.</strong> Making Wasm modules, as opposed to containers, the standard unit of compute and deployment would enable better scalability and security.</p>

<p>Such a transition isn’t going to happen overnight, so Wasm-based containerization will likely integrate into existing orchestration systems rather than attempting to replace Docker entirely.</p>

<p>I anticipate this space will be buzzing with activity over the next few years. A few projects are already leading the charge:</p>

<ul>
  <li>Microsoft Azure’s Deis Labs built <a href="https://krustlet.dev/">Krustlet</a>, which is a way to run Wasm workloads in existing Kubernetes clusters.</li>
  <li>Deis Labs also released <a href="https://github.com/deislabs/hippo">Hippo</a>, a Wasm-centric platform-as-a-service. I would guess that <a href="https://github.com/fermyon">Fermyon</a> is trying to commercialize that tech.</li>
  <li>With their <a href="https://github.com/wasmCloud/wasmCloud">wasmCloud</a> project, <a href="https://cosmonic.com/">Cosmonic</a> is building a platform and orchestration tier that combines Wasm containerization with the actor model for distributed systems.</li>
  <li>The <a href="https://github.com/lunatic-solutions/lunatic">Lunatic</a> platform also embraces the actor model and seems to have the best support running multiple light containers on top of a single WebAssembly runtime process.</li>
  <li><a href="https://suborbital.dev/">Suborbital</a>’s <a href="https://github.com/suborbital/atmo">Atmo</a> is another platform and orchestration system, but is more oriented towards serverless workloads.</li>
</ul>

<h3 id="faasserverless-platforms">FaaS/Serverless Platforms</h3>

<p>Function-as-a-service platforms need to execute user-provided code quickly and safely. Since serverless platforms are often used to run code for short durations, startup times are a particularly important metric. <strong>The ultra-fast cold starts and broad language support make Wasm a good choice for serverless workloads.</strong><sup id="fnref:8" role="doc-noteref"><a href="#fn:8" class="footnote" rel="footnote">8</a></sup></p>

<p>The CDN-edge computing platforms provided by <a href="https://blog.cloudflare.com/webassembly-on-cloudflare-workers/">Cloudflare Workers</a> and <a href="https://www.fastly.com/blog/how-compute-edge-is-tackling-the-most-frustrating-aspects-of-serverless">Fastly Compute@Edge</a> already provide the ability to run WebAssembly. Fastly claims 100x faster startup times than other offerings in the market, and attributes this speedup to their WebAssembly-based compiler and runtime. Netlify and Vercel are also building products in this space.</p>

<p>The serverless platforms built by major cloud providers aren’t far behind: AWS Lambda launched WebAssembly serverless functions a few months ago, and I expect that GCP and Azure will follow suit.</p>

<h3 id="blockchains">Blockchains</h3>

<p>Platforms like Ethereum and Solana provide a mechanism for users to write code, dubbed “smart contracts”, which can run on the blockchain. Ethereum built a fully custom system, creating a language called Solidity, a binary format for the compiled bytecode, and the Ethereum Virtual Machine for sandboxed execution. Solana opted to reuse some existing innovations, harnessing the LLVM compiler infrastructure to compile C, C++, or Rust code into a binary bytecode format inspired by the Berkeley Packet Filter, but still built their own runtime called Sealevel.</p>

<p>WebAssembly already provides much of this infrastructure: it lets users write code in the language of their choosing, provides compiler infrastructure to produce Wasm bytecode, and has numerous high-performance runtimes.</p>

<p>But if Ethereum and Solana have already built this infrastructure, what value does WebAssembly provide? The main value-add is actually around ecosystems. For example, Ethereum has its own language for writing smart contracts, which means that it is unable to leverage all the libraries and common functions that have been written in other languages. Solana is a bit better since it can use the Rust ecosystem. Assuming the technical challenges can be overcome, <strong>WebAssembly opens up smart contract development to a much wider audience and enables them to use the libraries and tools they’re already comfortable with.</strong></p>

<p>I am definitely not the first to make this realization. The Polkadot network, for example, uses a <a href="https://wiki.polkadot.network/docs/learn-wasm">WebAssembly-based virtual machine</a> as its runtime. The EOS virtual machine is also <a href="https://eos.io/news/eos-virtual-machine-a-high-performance-blockchain-webassembly-interpreter/">based on WebAssembly</a>. <a href="https://cosmwasm.com/">CosmWasm</a> uses it to build smart contracts that work across multiple blockchains. There was also a proposal called <a href="https://github.com/ewasm/design">eWASM</a> to replace the Ethereum Virtual Machine with a limited subset of WebAssembly, but it seems that effort has since fizzled out. The Wasmer runtime provides a “singlepass” compiler mode that is explicitly built for blockchains, while WasmEdge claims to have an Ethereum-compatible smart contract execution engine.</p>

<h2 id="predictions-and-opportunities">Predictions and Opportunities</h2>

<h3 id="a-new-application-architecture">A New Application Architecture</h3>

<p>Just as Docker could not replace virtual machines entirely, Wasm cannot replace Docker entirely. For instance, Docker containers can’t run custom OS kernels while virtual machines can. Similarly, Wasm containers can’t use some specialized CPU instructions, like x86’s 256-bit AVX instructions,<sup id="fnref:9" role="doc-noteref"><a href="#fn:9" class="footnote" rel="footnote">9</a></sup> and hence can’t compete with Docker on performance for some applications.</p>

<p>In my opinion, the set of workloads that Docker can support but Wasm can’t is currently larger than the analogous delta between Docker and virtual machines. However, Wasm is still a developing technology and hence will incrementally be able to address more types of workloads. Docker’s rise was closely coupled with the rise of microservice architectures. This took monolithic applications that were well-suited for virtual machines and replaced them with microservices that were well-suited for Docker containers. We’ll probably see <strong>a new application architecture that takes advantage of WebAssembly’s unique capabilities</strong>.</p>

<p>As per Conway’s law, an application’s architecture reflects the communication structure of the organization that produces it. Every new “reference architecture” throughout the history of computing has reduced the amount of coordination that is required between people. From mainframes to virtual machines to Docker containers, the number of people required to produce a deployable unit has progressively decreased. We’ve achieved this by decomposing systems into smaller and smaller components and by allowing the people building those components to work independently against well-defined interfaces. While microservices have decomposed monolithic applications into several small independent services, <strong>WebAssembly makes it easier to decompose microservices into even smaller components</strong>.<sup id="fnref:10" role="doc-noteref"><a href="#fn:10" class="footnote" rel="footnote">10</a></sup></p>

<p>What will this look like? Here’s a few possibilities:</p>

<ul>
  <li>When you split applications into the core business logic and the glue code needed to work with other systems, it turns out that the business logic is usually pretty small compared to the rest. By separating the interface of the glue code from the implementation of the capability it provides, it becomes possible to build business logic-centric applications and delegate the rest to external capability providers. Coupled with the long sidelined <a href="https://www.brianstorti.com/the-actor-model/">actor model</a>, this is the essence of <a href="https://github.com/wasmCloud/wasmCloud">wasmCloud</a>’s approach.</li>
  <li>Another possibility is that serverless architecture is the next step beyond microservices. Most services can be segmented into stateful and stateless portions, and the stateless portions can run as arbitrarily scalable serverless functions. In this case, WebAssembly serves as a convenient and easily scalable runtime for those serverless functions.</li>
  <li>WebAssembly might change the way we take on third-party dependencies. Modern code relies heavily on third-party libraries,<sup id="fnref:11" role="doc-noteref"><a href="#fn:11" class="footnote" rel="footnote">11</a></sup> and most of these dependencies are not vetted fully or frequently. As software supply chain issues like the recent <a href="https://en.wikipedia.org/wiki/Log4Shell">Log4j vulnerabilities</a> come to light, I expect people will start to take the security of third-party libraries more seriously. Approaches like Firefox’s use of Wasm and RLBox to isolate certain libraries will become more widespread. It might also be feasible to isolate third-party libraries into separate capability-constrained containers within the same Wasm runtime, assuming the performance limitations can be overcome.<sup id="fnref:12" role="doc-noteref"><a href="#fn:12" class="footnote" rel="footnote">12</a></sup></li>
</ul>

<h3 id="brownfield-deployment">Brownfield Deployment</h3>

<p>Wasm will eventually need to interoperate with Docker in some way. For the next couple years this is not strictly necessary, since Wasm will primarily be used in greenfield deployments with few requirements for backwards compatibility. But ultimately brownfield deployments need to be easy for Wasm to fully win the containerization race, especially in enterprise settings.</p>

<p>One potential outcome is that Docker will integrate a Wasm runtime. While plausible, I expect Wasm will be sufficiently differentiated to warrant separate tooling entirely. Instead, <strong>the unification of Docker and Wasm containers will happen at the orchestration layer</strong>.</p>

<p>It’s less clear if Kubernetes will effectively integrate Wasm-based execution or if a new orchestration system will emerge. On one hand, Kubernetes is currently the unrivaled king of orchestration. It has incredible momentum, and the Wasm containerization movement would be wise to ride on its coattails. Folks at Microsoft are investing in that future by building <a href="https://github.com/krustlet/krustlet">Krustlet</a>, which lets you run Wasm workloads in Kubernetes. On the other hand, Wasm code will have different requirements than Docker containers and hence Kubernetes might not be the right fit. For example, it would be useful to set up shared memory for inter-container communication when using Wasm-based third-party library isolation, which would be difficult to do with Kubernetes. Such Wasm-native orchestrators will eventually build bridges that ease migration from or integration with Docker.</p>

<p>While I’m hopeful for the upcoming wave of Wasm orchestrators, Kubernetes is sufficiently entrenched that it’s probably not going anywhere in the short-term.</p>

<h3 id="standardized-serverlessedge-framework">Standardized Serverless/Edge Framework</h3>

<p>Most serverless providers have their own framework for defining routes and lambda functions. Cloudflare, for instance, defines its own “cf” type and provides a CLI tool called wrangler for setting up code scaffolding. Fastly has its own set of interfaces for interacting with caches and logging, and AWS Lambda has a similar setup. The Fission framework for Kubernetes has its own set of libraries for integration with various languages. Some platforms try to circumvent this problem by letting users provide a Docker container, such that the platform only needs to handle execution. <a href="https://knative.dev/docs/">Knative</a> and <a href="https://fly.io/">Fly.io</a> both follow this approach. However, they must then keep a “warm pool” of workers to reduce the impact of cold start times, or pass this problem on to their users.</p>

<p>There’s an opportunity to build a standardized serverless function definition and deployment spec. The popular <a href="https://github.com/serverless/serverless">Serverless Framework</a> does a decent job at abstracting deployment, but still leaks provider-specific details into the function implementations. As soon as those details are abstracted away, multi-cloud deployments become much easier and hence the framework becomes much more powerful. It could eventually be like the Terraform of serverless.<sup id="fnref:13" role="doc-noteref"><a href="#fn:13" class="footnote" rel="footnote">13</a></sup></p>

<h3 id="package-management">Package Management</h3>

<p>Every programming language has an ecosystem around it. Most modern languages have a centralized package registry: Python has PyPI, NodeJS has npm, and Rust has Crates.io. Such registries, and the tooling and workflows that accompany them, are important to developing a high-quality ecosystem and making developer lives much easier.</p>

<p>For Wasm, the <a href="https://wapm.io/">WebAssembly Package Manager</a> (WAPM) promised to fill that gap. However, in practice the project seems largely dormant. At the time of writing, only three packages have been updated in the <a href="https://web.archive.org/web/20211229050050/https://wapm.io/">past two months</a>. The issue is that packages are supposed to build on each other, but WAPM only works well for standalone Wasm binaries with no inter-dependencies. The other option for a developer is to publish Wasm modules to npm, but of course this is not ideal for building a Wasm ecosystem beyond JavaScript or AssemblyScript as it does not encourage cross-language interoperability.</p>

<p>The issue isn’t really the fault of WAPM or npm, but rather a rough edge with WebAssembly itself.</p>

<blockquote>
  <p>attempting to write any non-trivial WebAssembly application that tries to interoperate across runtime or language boundaries requires significant effort today, and exchanging any non-fundamental data types (such as strings or structures) involves pointer arithmetic and low-level memory manipulation.</p>

  <p>― <a href="https://radu-matei.com/blog/intro-wasm-components/">Introduction to WebAssembly components | radu’s blog</a></p>
</blockquote>

<p>This is exactly the problem that the WebAssembly Component Model will solve. Wasm components standardizes the WebAssembly Interface format, and provides <a href="https://github.com/bytecodealliance/wit-bindgen">code generators</a> for both implementing and consuming those interfaces. In other words, it lets us easily cross runtime and language boundaries with Wasm.</p>

<p>There’s a big opportunity to build a high-quality package manager for WebAssembly. It should use Wasm components codegen to generate bindings for using Wasm modules from other languages. If the tooling is sufficiently good, it could make cross-language development a breeze, which would be the real unlock for the server-side WebAssembly ecosystem. The Wasm package registry could even syndicate across other package registries, automatically publishing packages with appropriate generated bindings to PyPI, npm, and Crates.io.</p>

<h2 id="conclusion">Conclusion</h2>

<p>At this point you’re probably thinking: if WebAssembly is so good, why isn’t it more widely used? Let me volunteer some responses:</p>

<ul>
  <li>WebAssembly’s marketing hasn’t been great. The name is a misnomer, since it is neither restricted to the web nor is it assembly. WebAssembly has primarily been marketed and <a href="https://blog.bitsrc.io/whats-wrong-with-web-assembly-3b9abb671ec2">pushed towards web developers</a>, but its real potential lies beyond the browser. The real unlock will come when C++ and Rust developers, en masse, start to recognize the potential that Wasm holds.</li>
  <li>WebAssembly standardization isn’t there yet. For example, the WebAssembly System Interface has numerous extensions that have not been officially standardized, but various runtimes implement a selection of these extensions. The promise of universal portability has not yet been fully realized.</li>
  <li>Cross-language interactions suck. We need WebAssembly components and good code generators for a critical mass of languages before people actually start to use Wasm across different languages.</li>
  <li>The developer experience leaves much to be desired. I’d love to see improvements in tooling, especially <a href="https://thenewstack.io/the-pain-of-debugging-webassembly/">around debugging</a>, and integrations with package managers, build systems, and IDEs.</li>
  <li>I hate to say this, but we probably need a few more severe software supply-chain incidents, of similar scale to Log4Shell, before WebAssembly’s library isolation capabilities are fully appreciated.</li>
</ul>

<p>WebAssembly has been deployed in a fairly impressive list of places and serves an assortment of use cases, but these represent isolated pockets of activity within the broader tech world. Among my friends, the small fraction who have heard of WebAssembly think it’s really exciting in principle, but are not building with it because it isn’t quite mature yet. However, many of these issues are being actively worked on and will probably reach an acceptable state within the next year or two. As such, it seems <strong>we’re on the brink of an explosion in WebAssembly activity, ecosystem, and community</strong>.</p>

<p><em>Discuss on <a href="https://news.ycombinator.com/item?id=30155295">Hacker News</a> or <a href="https://harshal.sheth.io/about">send me your thoughts</a>. Thanks to Nihar Sheth, Mohak Jain, Andrew Sun, and Michelle Fang for their feedback on early drafts of this article.</em></p>

<h2 id="notes">Notes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">

      <p>In reality, there’s a bit more nuance here. Wasm has two <a href="https://v8.dev/blog/emscripten-standalone-wasm">standard APIs</a>: Web APIs and WebAssembly System Interface (WASI) APIs. WebAssembly is also rapidly evolving, so there are a number of experimental features like threading that are widely implemented but not officially standardized. Beyond this, when Wasm is embedded within another application, that other application might also provide its own APIs. The Wasm code should execute if it only uses APIs and features that the runtime provides, which turns out to be a fairly reasonable assumption. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">

      <p>For some reason, it seems like every WebAssembly runtime name starts with “wa”. There’s also <a href="https://github.com/WasmEdge/WasmEdge">WasmEdge</a>, <a href="https://github.com/WAVM/WAVM">WAVM</a>, and <a href="https://github.com/wasm3/wasm3">wasm3</a>. One of the few exceptions is <a href="https://github.com/wasmx/fizzy">Fizzy</a>, but even that project is hosted by an organization called wasmx. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">

      <p>However, running interpreted languages, like Python and Ruby, or languages with complex runtimes, like Go, on Wasm probably won’t yield good performance. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">

      <p>In Firefox, Wasm compilation is actually <a href="https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/">faster</a> than the network delivers packets. The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming">WebAssembly.instantiateStreaming</a> method lets us begin the compilation process before we’ve even finished downloading the file, so there’s only minimal additional latency before the Wasm code starts executing. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">

      <p>Ok, there’s still some rough edges here. The capability-based permission systems aren’t super granular yet and most Wasm runtimes don’t protect against side-channel attacks like Spectre, so some additional strategies are necessary. Cloudflare’s Kenton Varda has written a <a href="https://blog.cloudflare.com/mitigating-spectre-and-other-security-threats-the-cloudflare-workers-security-model/">detailed article</a> on their threat model and the mitigations they’ve implemented. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">

      <p>Because Wasm can avoid issues of deoptimization that arise when deviating from the JavaScript “fast path”, it executes reliably fast while matching or exceeding JavaScript’s peak performance. Funnily enough, you can actually get JavaScript to run faster in a browser by compiling it to WebAssembly and running that instead. Lin Clark gave an <a href="https://www.youtube.com/watch?v=CRaMls9oVBw">excellent talk</a> on how and why this works. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7" role="doc-endnote">

      <p>From a personal conversation with a tech leader at Figma a few years back. Many browsers, including Google Chrome and Firefox, already use Skia under the hood. However, they do not expose Skia APIs directly and so applications must use the slower browser DOM or SVG interfaces. Figma draws directly to the browser canvas element, bypassing these slower layers, but still makes use of some Skia functionality by <a href="https://skia.org/docs/user/modules/canvaskit/">embedding</a> it within their application. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8" role="doc-endnote">

      <p>It’s worth noting that there are other alternatives. AWS Lambda, for example, uses the lightweight <a href="https://firecracker-microvm.github.io/">Firecracker MicroVM</a>. In a head-to-head, WebAssembly does have some <a href="https://arxiv.org/ftp/arxiv/papers/2010/2010.07115.pdf">performance advantages</a>. <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9" role="doc-endnote">

      <p>WebAssembly SIMD support for 128-bit types is currently experimental, but is implemented in many browsers and runtimes. “Long SIMD” support <a href="https://github.com/WebAssembly/design/blob/5b2c607fe173c813214afde33e0ea82d33dd0983/FutureFeatures.md#long-simd">remains for future work</a>. <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10" role="doc-endnote">

      <p>The counterargument to this claim is that “just because we can decompose microservices doesn’t mean we will”. This is certainly a valid criticism, and is actually one that has also been applied to monoliths in the context of microservices. For instance, a recent <a href="https://arnoldgalovics.com/microservices-in-production/">article</a> titled “Don’t start with microservices in production – monoliths are your friend” generated a ton of <a href="https://news.ycombinator.com/item?id=29576352">discussion</a>. Nevertheless, microservice architectures have been tremendously effective and productive when deployed in fitting circumstances, and I expect the same will be true with WebAssembly-centric architectures. It’s less clear how many fitting circumstances exist in the context of WebAssembly. <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:11" role="doc-endnote">

      <p>This problem is so well-known that there are even <a href="https://www.reddit.com/r/ProgrammerHumor/comments/6s0wov/heaviest_objects_in_the_universe/">memes</a> <a href="https://xkcd.com/2347/">about</a> <a href="https://www.reddit.com/r/ProgrammerHumor/comments/992u1p/dependencies_101/">it</a>. <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:12" role="doc-endnote">

      <p>Someone should probably build a company that does this. Software supply-chain is top-of-mind right now given the recency of the Log4j vulnerability, and it seems like most existing companies are focused on verifying dependencies instead of isolating them. <a href="#fnref:12" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:13" role="doc-endnote">

      <p>Perhaps this isn’t the best comparison, since Terraform only abstracts the deployment and interactions with cloud providers whereas this framework would operate at the code layer as well. <a href="#fnref:13" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Read in Japanese thanks to the folks at POSTD.]]></summary></entry></feed>