Skip to content

Instantly share code, notes, and snippets.

@manveru
Created April 16, 2009 05:01
Show Gist options
  • Save manveru/96232 to your computer and use it in GitHub Desktop.
Save manveru/96232 to your computer and use it in GitHub Desktop.
From 69c54b3b56d6ca1e51cfe33a15a4a5b730cc82ec Mon Sep 17 00:00:00 2001
From: Michael Fellinger <[email protected]>
Date: Thu, 16 Apr 2009 13:57:17 +0900
Subject: [PATCH] Improved Rack::Reloader
---
lib/rack/reloader.rb | 134 +++++++++++++++++++++++++++++++++-----------------
1 files changed, 88 insertions(+), 46 deletions(-)
diff --git a/lib/rack/reloader.rb b/lib/rack/reloader.rb
index b17d8c0..aa2f060 100644
--- a/lib/rack/reloader.rb
+++ b/lib/rack/reloader.rb
@@ -1,64 +1,106 @@
-require 'thread'
+# Copyright (c) 2009 Michael Fellinger [email protected]
+# All files in this distribution are subject to the terms of the Ruby license.
+
+require 'pathname'
module Rack
- # Rack::Reloader checks on every request, but at most every +secs+
- # seconds, if a file loaded changed, and reloads it, logging to
- # rack.errors.
- #
- # It is recommended you use ShowExceptions to catch SyntaxErrors etc.
+ # High performant source reloader
+ #
+ # This class acts as Rack middleware.
+ #
+ # What makes it especially suited for use in a production environment is that
+ # any file will only be checked once and there will only be made one system
+ # call stat(2).
+ #
+ # Please note that this will not reload files in the background, it does so
+ # only when actively called.
+ #
+ # It is performing a check/reload cycle at the start of every request, but
+ # also respects a cool down time, during which nothing will be done.
class Reloader
- def initialize(app, secs=10)
+ def initialize(app, cooldown = 10, backend = Stat)
@app = app
- @secs = secs # reload every @secs seconds max
- @last = Time.now
+ @cooldown = cooldown
+ @last = (Time.now - cooldown)
+ @cache = {}
+ @mtimes = {}
+
+ extend backend
end
def call(env)
- if Time.now > @last + @secs
- Thread.exclusive {
- reload!(env['rack.errors'])
- @last = Time.now
- }
+ if @cooldown and Time.now > @last + @cooldown
+ if Thread.list.size > 1
+ Thread.exclusive{ reload! }
+ else
+ reload!
+ end
+
+ @last = Time.now
end
@app.call(env)
end
- def reload!(stderr=$stderr)
- need_reload = $LOADED_FEATURES.find_all { |loaded|
- begin
- if loaded =~ /\A[.\/]/ # absolute filename or 1.9
- abs = loaded
- else
- abs = $LOAD_PATH.map { |path| ::File.join(path, loaded) }.
- find { |file| ::File.exist? file }
- end
-
- if abs
- ::File.mtime(abs) > @last - @secs rescue false
- else
- false
- end
- end
- }
-
- need_reload.each { |l|
- $LOADED_FEATURES.delete l
- }
-
- need_reload.each { |to_load|
- begin
- if require to_load
- stderr.puts "#{self.class}: reloaded `#{to_load}'"
- end
- rescue LoadError, SyntaxError => e
- raise e # Possibly ShowExceptions
+ def reload!(stderr = $stderr)
+ rotation do |file, mtime|
+ previous_mtime = @mtimes[file] ||= mtime
+ safe_load(file, mtime, stderr) if mtime > previous_mtime
+ end
+ end
+
+ # A safe Kernel::load, issuing the hooks depending on the results
+ def safe_load(file, mtime, stderr = $stderr)
+ load(file)
+ stderr.puts "#{self.class}: reloaded `#{file}'"
+ file
+ rescue LoadError, SyntaxError => ex
+ stderr.puts ex
+ ensure
+ @mtimes[file] = mtime
+ end
+
+ module Stat
+ def rotation
+ files = [$0, *$LOADED_FEATURES].uniq
+ paths = ['./', *$LOAD_PATH].uniq
+
+ files.map{|file|
+ next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
+
+ found, stat = figure_path(file, paths)
+ next unless found and stat and mtime = stat.mtime
+
+ @cache[file] = found
+
+ yield(found, mtime)
+ }.compact
+ end
+
+ # Takes a relative or absolute +file+ name, a couple possible +paths+ that
+ # the +file+ might reside in. Returns the full path and File::Stat for the
+ # path.
+ def figure_path(file, paths)
+ found = @cache[file]
+ found = file if !found and Pathname.new(file).absolute?
+ found, stat = safe_stat(found)
+ return found, stat if found
+
+ paths.each do |possible_path|
+ path = ::File.join(possible_path, file)
+ found, stat = safe_stat(path)
+ return ::File.expand_path(found), stat if found
end
- }
+ end
- stderr.flush
- need_reload
+ def safe_stat(file)
+ return unless file
+ stat = ::File.stat(file)
+ return file, stat if stat.file?
+ rescue Errno::ENOENT, Errno::ENOTDIR
+ @cache.delete(file) and false
+ end
end
end
end
--
1.6.2.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment