Skip to content

uWSGI

 5 min Learning through a server I wanted to use for almost everything: embedded files, Python entry points, packet boundaries, and the order things came alive.
Published

Nov 20, 2012

I learned an absurd amount through uWSGI. Every line of code was like a new present to unwrap.

I learn by exposure. Following uWSGI’s implementation took me into processes, interpreters, sockets, binary layouts, file descriptors, plugins, and packaging. I kept finding another thing it could do and trying to connect it to whatever I was building. Sometimes the connection was useful. Sometimes it became an experiment of its own.

Threesixty used uWSGI to run the traffic reader and serve the player map. GridFUSE began after its GridFS plugin made me want to experiment with files in MongoDB. Zippy eventually folded the server into a static Python application. Those projects make more sense with this period of curiosity behind them.

I worked across several parts of the server, learning from Roberto De Ioris and the other upstream developers along the way.

A server that could unpack itself

I added --extract. uWSGI already had a reader that understood multiple address schemes. The new option passed an address into it, wrote the result to standard output, and exited:

buf = uwsgi_open_and_read(address, &len, 0, NULL);

The submission showed reads from embedded data, symbols, HTTP, and an executable section. One example extracted a root filesystem archive from the running program before reading its configuration. That was the appealing possibility: the same artifact could contain the server, configuration, and files needed to start the application.

The change was small because the interesting reader already existed. It was registered as an immediate option, so invoking it did not require bringing up the normal serving loop. Upstream merged it that day.

I also found a limit in that shared reader. Reading /proc/sys/net/core/somaxconn as configuration returned an empty value because the procfs file reported size zero. The later report and repair are a separate note. The address abstraction was useful precisely because fixing it could improve every caller.

Four bytes in a signed packet

A subscription fix changed three lines. The key unix had been passed with length six instead of four. The signing call included a four-byte protocol header that the verification side did not include. The transmitted payload length also needed its four-byte adjustment.

The corrected signing range was:

uwsgi_rsa_sign(sign, ub->buf + 4, ub->pos - 4, &signature_len)

The report described failed subscriptions. The signature machinery was present, but sender and receiver disagreed about which bytes it covered. The patch repaired that disagreement and merged shortly after submission.

Reading a real buffer construction made an offset stop being an abstract integer. It selected the message the other side would authenticate.

Which program was starting?

I proposed making a uWSGI binary invoked under a name beginning with python enter Py_Main. A symlink could select the interpreter-facing personality while keeping the embedded uWSGI API available.

The proposal had to preserve the original argument count, keep uWSGI from consuming Python’s arguments, honor standard input, and restore Python’s interrupt handler before entering its main function. Calling Py_Main was easy to write down; retaining the interpreter’s surrounding behavior was the real work. The proposal did not land.

A different direction did land later: building uWSGI as a CPython extension. The interpreter could import the server, set it up, initialize it, and run it. The patch preserved original and replacement argument vectors, provided setuptools build integration, and refused repeated setup or setup from a worker. Turning a process-oriented server into an import did not make its initialization safely repeatable.

binary_argc dealt with another consequence. An embedded launch could have several leading arguments that belonged to the Python invocation. The server needed to preserve them for reload while excluding them from its own option parser. A count identified that prefix, and the parser temporarily received a joined display name in place of the prefix. The real argument entry was restored afterward.

That change merged. The public Zippy patches carry the other side of this work: an application containing the interpreter, which in turn contained the server.

Reuse also has a startup order

uWSGI’s background workers called mules could already run Python files. I added module:callable targets by reusing the existing mount loader. Resolve the callable through that loader, acquire the interpreter lock, call it without arguments, and release the returned references.

The next day’s follow-up moved the loader table’s initialization earlier. The mule path could need it before ordinary application initialization had populated it. Both patches merged. Using the shared loader was the right shape, but only once that loader existed at the time the new caller needed it.

Even a smaller routing addition carried an ownership rule. My upper and lower variables allocated buffers of the request value’s exact length and registered them with need_free = 1. The routing engine then knew it owned cleanup. The transform was bytewise; this was not a Unicode case-conversion layer.

The experiments around the patches

The 1.9.6 release notes credit my suggestion for running IPython through --pyshell. A later saved session shows it combined with the pty plugin: a server build ending at an interactive Python prompt. Another reproducer ran Celery inside uWSGI and isolated an event-serialization failure.

These were ways to inspect a running system and find out what its pieces would tolerate. They also produced useful reports when an assumption failed. Some work became upstream code, some stayed in my projects, and some remained open questions.

I could follow a feature down into its implementation, try something adjacent, and come back with either a patch or a better question. I had a lot of fun doing that.