Magical Reloading Sparkles

A little background

I write web applications in sinatra. It’s great for the small applications I use to make my days a little smoother. I currently have an app to track and graph my weight, another which acts as a simple feed aggregator and a third for jotting down random notes. I hope to expand the repertoire fairly soon with an addressbook application and perhaps a personal wiki. To serve the sinatra applications, I use unicorn. It’s a rack webserver, based on the fork model. So you have one master process, and at least 1 worker which is forked from the master process. The worker actually deals with the requests, the master just hands them out.

With those introductions out the way, lets get to the point. One “problem” with developing on sinatra is it has no code reloading. Code reloading is the process which lets you change a file on the disk and have those changes reflected in the running webserver without needing to manually restart the whole thing. It saves a lot of tabbing about and repetitive effort. I say “problem” because there are a few solutions out there for it. Shotgun was one of the first. It forks a new process to handle each request, guaranteeing a clean memory space for each. This process loads the code and serves the request. This works, but can get very slow. Especially if, as is usual in development, CSS and javascript are being served by the rack process; shotgun doesn’t care, it forks anyway. Another solution is rerun. Rerun watches the filesystem for changes, restarting the whole application when it sees them. This is generally a better solution than shotgun’s restart model, and I used it for a while; restarting takes longer, but its done less often. There is also sinatra-reloader which is sinatra specific, and I haven’t tried. There are probably more, too.

My own solution

rerun has one problem, from a certain point of view: It does too much work. Since it reloads the whole application, it reloads all the gems and other library code as well. If you’re using the array of gems ruby offers to you, this can be a significant part of the boot time. And that’s just unneeded; code in gems is generally static, at least over the timescale of a coding session.

Enter unicorn. I mentioned earlier that the worker process is forked from the master process. Unless you set preload_app true, the worker loads the sinatra application when it forks; the master has no copy of the application code in this case and each worker is a clean slate.

What is forking? The fork(2) system call creates a duplicate of the parent process with its own pid, but shared file descriptors and more importantly, with the same structures loaded in memory. In linux, this is literally the same memory, the kernel implements copy-on-write, so only changed pages are actually in separate memory locations. If this is combined with the fact that ruby doesn’t require the same file twice, any code we load in the master process is code the worker doesn’t have to. And unicorn helpfully provides a before_fork hook.

The important details in this file are the list of gems and libraries to require in the before_fork hook. This could easily be replaced with a require statement to a more centrally located file the application uses. These files will only be required once per session, so it should only be used for stable code; only you can decide what that means for your app. For details of the other options, see the configurator docs.

Now for the reloading. One great thing about unicorn is it listens to signals. These can be used to control it in a number of ways, but the one we’re interested in is SIGHUP. This makes unicorn respawn its workers, which will reload the application. So now all that’s needed is something to watch the appliction for changes, and do something about that. This is accomplished with the excellent directory_watcher gem, which hooks into the kernel’s file notification system, so no loops for polling on file mtime.

This script starts the application using a daemonized unicorn. It then sets up a couple of watchers. The first one looks at the logfile unicorn provides, spooling that out into the terminal as lines are written to the file. The second watches a glob of files, reloading the application as they change. But thanks to the prefork model of unicorn, only your application code needs reparsing. On my laptop, this cuts restart time approximately in half.

Closing remarks

This solution works for me, but it is a little rough around the edges. The file watching script could probably be cleaned a lot, and use some command line option parsing to make it more flexible. As it stands, the script lives in the application root.

Another problem is if you introduce any errors on loading files, the worker process will thrash. It tries to load, dies on loading, and then gets respawned; repeat ad infinitum. It would be neat if some logic could be added to stop this somehow.

But other than that, it works great. And it was fun putting the pieces together.

Edit: Thanks to khaase for pointing out a few simplifications to the watcher script.

Tagged rack ruby unicorn

Comments (0)

Add a Comment

Meta
Share