Sunday, May 26, 2013

Code reloading

For a web framework, code reloading is a crucial feature (especially for a framework designed for rapid development!). Minutes spent waiting for a server to reload add up quickly. Some of that can be mitigated by doing comprehensive unit testing, but there is no replacement for being able to see your code running in situ.

There's a great explanation of code reloading by Konstantin Haase, some of which I actually understand! The rails deep-diving isn't what I'm interested in, but the explanation of code reloading. This is also the link that Sinatra uses to explain code reloading, so I'm not the only person that uses it as a reference.

The bottom line is that if you want true code reloading, you need to actually have a separate Ruby context for each request - which is what Shotgun (or shotgonne if you're a Prachett fan) does behind the scenes. It's the only real way to guarantee that your code reloads.

Initially the way Kul did code reloading was just to re-load the files every time it loaded one. It's both brute-force and has a few problems. In production, you can't reload the entire website from disk for every request, but in development mode it really isn't that big of an issue.

The bigger issue is that I was basically abusing the open nature of ruby classes. Effectively, the load will reopen the class and redefine it from the code in the file. This works well for a few specific things, and badly (or not at all) for others. For example, adding a method would work fine; removing a method would not work at all.

Plus, as much as I justified it, reloading all of the code on every request did bother me.

Today I started replacing the reloading inside of Kul. It's a small improvement, but I think it should help in the long run. There are some limitations, but it should work for 99% of the expected use of the framework. In the other circumstances, you'll just have to restart the server - sorry.

The first thing I wanted to fix was the brute-force nature of the code reloading. That was simple enough - I just keep track of the file date and don't bother reloading the file if it hasn't been modified. I did find out that (at least on Windows) the modified time is only tracked to the second - I'm sure that's fine for almost everyone else but I had to do some jiggery-pokery to trick the unit tests into passing. It was either that or put a one-second sleep in there, and that's just wrong.

The second thing I did was to assume that require statements are not likely in user code. That's probably the most fragile assumption I'm making, but it should cover most of the usages (once I get the models in, at least). As long as all of the code you're using is either:

  1. inside of a library (which shouldn't change during runtime), or
  2. in framework files such as server.rb or controller.rb
then the framework should be able to gracefully handle changes to your code. Basically, the user has to follow the framework's rules as far as naming files and placing code. 

(This is actually the same assumption I was making before, but I made a more conscious decision whereas before it was just incidental)

There are a few limitations even with that big 'ol assumption up there. If you have more than the framework-expected class in the file, you'll be falling back on the original code reloading (i.e. it won't remove methods / constants / etc). Also, any metaprogramming you've done outside of that file will be gone. Basically don't manipulate classes outside of the file.

Also, any instances hanging around will not change their code. That's the one that I think is most likely to cause confusion. Code reloading could also do some really interesting things with class methods, depending on how they're called. And lastly, this code reloading will work horribly in a multi-threaded environment. I think that it would eventually reach a steady-state, but it's hard to say for sure.

At the end of the day, it's not perfect, but it should make development easier, and that's the final goal. It's not for production anyway. The reloading code will be exposed so that if anyone does want to make use of it from their application, they can call Kul::FrameworkFactory.load_class with the class and path, and the framework will handle the reloading.

No comments:

Post a Comment