reloader.rb 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. # Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
  2. # Rack::Reloader is subject to the terms of an MIT-style license.
  3. # See COPYING or http://www.opensource.org/licenses/mit-license.php.
  4. require 'pathname'
  5. module Rack
  6. # High performant source reloader
  7. #
  8. # This class acts as Rack middleware.
  9. #
  10. # What makes it especially suited for use in a production environment is that
  11. # any file will only be checked once and there will only be made one system
  12. # call stat(2).
  13. #
  14. # Please note that this will not reload files in the background, it does so
  15. # only when actively called.
  16. #
  17. # It is performing a check/reload cycle at the start of every request, but
  18. # also respects a cool down time, during which nothing will be done.
  19. class Reloader
  20. def initialize(app, cooldown = 10, backend = Stat)
  21. @app = app
  22. @cooldown = cooldown
  23. @last = (Time.now - cooldown)
  24. @cache = {}
  25. @mtimes = {}
  26. extend backend
  27. end
  28. def call(env)
  29. if @cooldown and Time.now > @last + @cooldown
  30. if Thread.list.size > 1
  31. Thread.exclusive{ reload! }
  32. else
  33. reload!
  34. end
  35. @last = Time.now
  36. end
  37. @app.call(env)
  38. end
  39. def reload!(stderr = $stderr)
  40. rotation do |file, mtime|
  41. previous_mtime = @mtimes[file] ||= mtime
  42. safe_load(file, mtime, stderr) if mtime > previous_mtime
  43. end
  44. end
  45. # A safe Kernel::load, issuing the hooks depending on the results
  46. def safe_load(file, mtime, stderr = $stderr)
  47. load(file)
  48. stderr.puts "#{self.class}: reloaded `#{file}'"
  49. file
  50. rescue LoadError, SyntaxError => ex
  51. stderr.puts ex
  52. ensure
  53. @mtimes[file] = mtime
  54. end
  55. module Stat
  56. def rotation
  57. files = [$0, *$LOADED_FEATURES].uniq
  58. paths = ['./', *$LOAD_PATH].uniq
  59. files.map{|file|
  60. next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
  61. found, stat = figure_path(file, paths)
  62. next unless found && stat && mtime = stat.mtime
  63. @cache[file] = found
  64. yield(found, mtime)
  65. }.compact
  66. end
  67. # Takes a relative or absolute +file+ name, a couple possible +paths+ that
  68. # the +file+ might reside in. Returns the full path and File::Stat for the
  69. # path.
  70. def figure_path(file, paths)
  71. found = @cache[file]
  72. found = file if !found and Pathname.new(file).absolute?
  73. found, stat = safe_stat(found)
  74. return found, stat if found
  75. paths.find do |possible_path|
  76. path = ::File.join(possible_path, file)
  77. found, stat = safe_stat(path)
  78. return ::File.expand_path(found), stat if found
  79. end
  80. return false, false
  81. end
  82. def safe_stat(file)
  83. return unless file
  84. stat = ::File.stat(file)
  85. return file, stat if stat.file?
  86. rescue Errno::ENOENT, Errno::ENOTDIR
  87. @cache.delete(file) and false
  88. end
  89. end
  90. end
  91. end