Lua Log #1: Event Loop Adventures

Last summer, I went on a beach vacation with my family, and I needed something to do when we weren’t doing family things. My primary software skills are in the Python and Django world, and I decided that I want to something very different to stretch my brain in new ways. On a whim, I thought it might be fun to make a web framework in Lua.

Lua is a very compact, interpretted language that originates from a group of academics at a university in Brazil. Lua itself is wildly popular as an embedded language and can be found in all sorts of games and other tools. Lua also has a reputation for being an extremely fast interpretted language.

I played with Lua a few years ago and produce a Pong clone using the LÖVE 2D game engine. With my experience building that game, I recall that my tests finished in a blindingly fast amount of time. This memory is probably what led to my “random” choice when picking to build a Lua web framework.

My thinking for this project is something like this:

  • Learning to make all the pieces of a web framework could be a lot of fun.
  • If something fast and useful comes out the other side, great!

My first foray into this project started with a template rendering engine. Rather than go with regex pattern matching, I decided to go into hard mode and pick a PEG parser. I spent my beach vacation reading white papers and twisting my mind to understand how this kind of parser works.

After a few months of dabbling with the rendering engine, I felt like I was really limiting what I was able to see. With the engine, I reached a level where I will have to implement much of the expression grammar needed by Lua in order to parse a template properly and produce a render function that will perform its task properly. That part of the project quickly got into the weeds, so I made the call to pull back and think on different areas of the project.

Web server

Eventually, I remembered The Pragmatic Programmer and the discussion of tracer bullets. I needed something that can serve a small slice of functionality that can go all the way through the system. Building up this minimal working web framework should allow me to adapt to the direction I want to go. This is the idea behind a tracer bullet. Tracer bullets are used to find a target quickly by revealing where a weapon is aiming in a visible way and making adjustments after that to reach the target.

If I can build a crude web framework that reaches the target somewhat, i.e. responding to a browser request, then I can fill in the details after that minimal point.

In researching the Lua ecosystem, I found nothing like the WSGI/ASGI approach of the Python web world. There is no “standard” app server interface. Because of this, there is no common web server that can handle a common web app format. In other words, I could build a web framework, but I had nothing standard to plug it into.

Thus, my project grew in scope. In addition to building a web framework, I’ve decided to try my hand a building a web server too. Here is where choice starts to come into the picture.

  • Do I build a web server that uses a threaded model where a main process farms out connections to a set of worker processes?
  • Do I build an asynchronous web server that uses an event loop and runs with cooperative scheduling?

I guess the title of this article is a spoiler. I went with the async event loop pattern.

Event loop

The best known aysnc programming model in the web software world is undoubtedly Node.js. After some research, it became clear to me that the underlying event loop for Node.js call libuv is an equally popular choice when considering this async style of programming.

The popular Python async web server, Uvicorn, takes this exact approach of implementing a web server on top of libuv.

I picked libuv as my choice, but then I needed a way to include it into my Lua project. Thankfully, there exists a Lua web framework named Luvit that followed a similar path. Because libuv is a C library, the Luvit developers built a binding libary to expose libuv as a Lua library. I’m immensely grateful that I don’t have to write that binding layer myself (my C skills are very rusty and I was never fantastic at C to begin with).

One big challenge with libuv is that it operates with a callback model. In order to enable cooperative scheduling, any call that would be blocking in libuv like network I/O expects to receive a callback.

I would like users (if ever there are any) of my web framework to be able to avoid callbacks. The Uvicorn project is able to get around these callbacks thanks to the amazing work of uvloop. uvloop integrates libuv into Python’s asyncio module.

Here’s where I’m starting to run into challenges. Lua’s standard library is very small. There is no concept like asyncio. There are also no Lua language constructs like async/await. Lua has coroutines as its mechanism for handling cooperative scheduling. I’ve basically spent my entire weekend researching to find good ways of using coroutines to hide callbacks from calling code. Ultimately, I founds some similar patterns in Luvit that I’m hoping to replicate to a degree.

My goal now to make a web server that uses libuv and make an API that feels like ASGI. Since I don’t have the native async and await used by ASGI in Python, maybe I’ll spawn a variant of Lua ASGI. Perhaps I should call it LASGI.

Where I’m at is building this bridge from callbacks to coroutines. Once I have that bridge, I’ll probably focus on HTTP 1.1 parsing to get the rough support that is required by the ASGI interface.

In the spirit of having a tracer bullet, when I have the most crude thing possible, I’ll switch back to the framework side and start handling ASGI messages. That will likely mean building a request router and some kind of request controller or handler.

I don’t know how often I’ll write this kinds of entries. I thought it might be useful to expose others to the messy process of building a project. If you’re looking for the code and all my notes, you can find my project on GitHub.