Created
April 16, 2009 05:01
-
-
Save manveru/96232 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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