|Val Packett 18d215374b|
A simple modern tool for running web applications on demand and stopping them after a period of inactivity.
Back in the day, web apps used something like CGI or inetd, where one request got one UNIX process that would, well, process it — and quit. This meant that your whole app would be loaded and unloaded once per request, which is especially slow when you use big frameworks written in scripting languages. But it also meant that when no requests were being processed, no resources (such as RAM) were held. Of course, these days no one cares about not holding resources and this approach is completely dead OH WAIT NO IT'S THE COOL NEW THING.
But what I want to do is to run many rarely used services on a VPS with low RAM. Modern web app servers are designed to run on a socket until you stop them. So what if I want to stop inactive services?
Should work on any modern UNIX-like system.
$ cc -O2 -fPIE -pie -fstack-protector-all -lpthread -o soad soad.c
soad where you keep your binaries.
First, your web server needs to support socket activation.
Either by explicitly specifying a file descriptor via something like an
fd://3 argument, or by using the tiny metadata protocol from systemd — the
LISTEN_FDNAMES environment variables.
It also needs to shut down gracefully on a signal (
SIGTERM is sent by default, customize by passing the signal number as
Some libraries and apps for that:
- Ruby: Puma (see examples below. tl;dr needs the same socket path specified, it literally checks the path. also bundler exec needs
- Python: gunicorn
- Python/Lua/Perl/Ruby/etc.: uWSGI (see examples below. tl;dr needs an obscure option to shut down gracefully on SIGTERM. it can also do this sort of thing on its own, but in a more complex and memory consuming way)
- Node.js: socket-activation (note: needs a socket name. obviously, it is
- Go: go-systemd/activation or systemd.go; + manners for graceful shutdown
- Rust: listenfd
- Haskell: socket-activation, wai-cli (based on socket-activation, has graceful shutdown)
(You can implement your own in 5 minutes, all you need to do is create a socket object from a file descriptor. See
test_server.rb for a tiny example.)
Now, to run your app on a UNIX domain socket, something like this:
$ soad -s /var/run/myapp.sock -t 240 -- bundle exec --keep-file-descriptors puma -b unix:/var/run/myapp.sock $ soad -s /var/run/pyapp.sock -t 240 -- gunicorn app:app $ soad -s /var/run/pyapp.sock -t 240 -- uwsgi --master --hook-master-start "unix_signal:15 gracefully_kill_them_all" --wsgi-file app.py --callable app --lazy-apps
--time-until-stop argument is the number of seconds the app will be allowed to run without any activity.
(Activity is determined by incoming socket connections, so basically the number of seconds since the last incoming request.)
Point your reverse proxy to that socket and enjoy.
Please feel free to submit pull requests!
By participating in this project you agree to follow the Contributor Code of Conduct and to release your contributions under the Unlicense.
Please run clang static analysis when testing your changes, something like that:
$ scan-build14 clang14 -Weverything -Wthread-safety -fsanitize=address -lpthread -o soad soad.c
And use clang-format to format the code.
This is free and unencumbered software released into the public domain.
For more information, please refer to the
UNLICENSE file or unlicense.org.