madlep.com

Julian Doherty. Coder. Ruby, Elixir, Javascript, Whatever.

Rack::Reloader Not Reloading? There's `to_app` for That

I burnt a bunch of time over the last few days trying to get the Rack::Reloader middleware to… you know… reload stuff.

TL;DR – call to_app on your Rack::Builder block

I had a Rack app that was being mounted in a parent Rails app. Not wanting to stop/start Rails whenever I tweaked that little Rack app, I figured I’d try to get code in the app auto loading after each request. Tried (and failed) to do it as part of the parent Rails app, so I added use Rack::Reloader in to the middleware stack.

Because this is a mapped rack app, it didn’t have it’s own config.ru to set up the middleware. Instead, the Rack app that gets mounted in Rails routes.rb file is an instance of Rack::Builder. Something like this:

hello_rack.rb
1
2
3
4
5
class HelloRack
  def self.call(env)
    [200, {}, ["Hello, World"]]
  end
end
my_app.rb
1
2
3
4
5
6
7
require 'hello_rack'

# WARNING! This doesn't reload... don't copy+paste this as the solution. Read on for the answer!
MyApp = Rack::Builder.new do
  use Rack::Reloader
  run HelloRack
end

And then in our Rails routes file, we hook it all up

config/routes.rb
1
2
3
4
5
Rails.application.routes.draw do
  ...
  mount MyApp, at: "/my_app".
  ...
end

This all looks good, and when we change our code in hello_rack.rb it should pick it up, right?…

So I try it out. Then… nothing. No reloading… No reason why.

Time to crack open the Rack source code…

debugging

Later that day…

It turns out that using the Rack::Builder instance directly as the Rack app causes it to recreate the middleware stack every time a request is made – which calls .new on each middleware class defined.

Normally this is fine, but Rack::Reloader keeps state of what source files are loaded, and the last modified time of them. If the Rack::Reloader instance doesn’t know about the file, it records it’s current modified time, and moves on without reloading it – the assumption being that if the app has just started, then the file doesn’t need to be reloaded – because it’s only just been loaded.

Except it hasn’t just been loaded – Rack::Reloader only thinks it has, as it’s a brand new instance, and this is the first time it’s ever seen the file. By creating a new Rack::Reloader instance for every request, all the state about file modified time gets thrown away, and nothing ever gets reloaded.

The fix? #to_app

The fix for this is really easy. Just call Rack::Builder#to_app and use that instead of using the Rack::Builder instance directly.

#to_app generates the Rack app with all the mappings and middleware in place – which is what we want – but only once. Rack::Builder#call delegates to that every time causing brand new middelware to be instantiated for every request (I’m not sure if that is a bug or a feature…)

If we change our my_app.rb file to look like this: everything works beautifully.

my_app_with_proper_reloading.rb
1
2
3
4
5
6
require 'hello_rack'

MyApp = Rack::Builder.new do
  use Rack::Reloader
  run HelloRack
end.to_app # note `to_app` here - that's the secret sauce

Note that you won’t see this problem if you’re just writing a rackup file (i.e. config.ru) and using rackup to execute that – rackup calls Rack::Server.start – which does exactly this under the hood.

Programming – minutes of addictive joy from the flow of writing code, then hours of staring at the screen swearing trying to untangle which library is making everything behave weirdly.