Class: Mizuno::Reloader

Inherits:
Object
  • Object
show all
Defined in:
lib/mizuno/reloader.rb

Overview

Middleware for reloading production applications; works exactly like Rack::Reloader, but rather than checking for any changed file, only looks at one specific file.

Also allows for explicit reloading via a class method, as well as by sending a SIGHUP to the process.

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, interval = 1) ⇒ Reloader

Returns a new instance of Reloader.



34
35
36
37
38
39
40
41
# File 'lib/mizuno/reloader.rb', line 34

def initialize(app, interval = 1)
    Reloader.add(self)
    @app = app
    @interval = interval
    @trigger = self.class.trigger
    @logger = self.class.logger
    @updated = @threshold = Time.now.to_i
end

Class Attribute Details

.loggerObject

Returns the value of attribute logger.



20
21
22
# File 'lib/mizuno/reloader.rb', line 20

def logger
  @logger
end

.reloadersObject

Returns the value of attribute reloaders.



20
21
22
# File 'lib/mizuno/reloader.rb', line 20

def reloaders
  @reloaders
end

.triggerObject

Returns the value of attribute trigger.



20
21
22
# File 'lib/mizuno/reloader.rb', line 20

def trigger
  @trigger
end

Class Method Details

.add(reloader) ⇒ Object



27
28
29
30
31
32
# File 'lib/mizuno/reloader.rb', line 27

def Reloader.add(reloader)
    Thread.exclusive do
        @logger ||= Mizuno::Server.logger
        @reloaders << reloader
    end
end

.reload!Object



23
24
25
# File 'lib/mizuno/reloader.rb', line 23

def Reloader.reload!
    reloaders.each { |r| r.reload!(true) }
end

Instance Method Details

#call(env) ⇒ Object

Reload @app on request.



46
47
48
49
# File 'lib/mizuno/reloader.rb', line 46

def call(env)
    Thread.exclusive { reload! }
    @app.call(env)
end

#find(file, paths) ⇒ Object

Takes a relative or absolute file name, a couple possible paths that the file might reside in. Returns a tuple of the full path where the file was found and its modification time, or nil if not found.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/mizuno/reloader.rb', line 120

def find(file, paths)
    if(Pathname.new(file).absolute?)
        return unless (timestamp = mtime(file))
        @logger.debug("Found #{file}")
        [ file, timestamp ]
    else
        paths.each do |path|
            fullpath = File.expand_path((File.join(path, file)))
            next unless (timestamp = mtime(fullpath))
            @logger.debug("Found #{file} in #{fullpath}")
            return([ fullpath, timestamp ])
        end
        return(nil)
    end
end

#find_files_for_reloadObject

Walk through the list of every file we’ve loaded.



91
92
93
94
95
96
97
# File 'lib/mizuno/reloader.rb', line 91

def find_files_for_reload
    paths = [ './', *$LOAD_PATH ].uniq
    [ $0, *$LOADED_FEATURES ].uniq.map do |file|
        next if file =~ /\.(so|bundle)$/
        yield(find(file, paths))
    end
end

#mtime(file) ⇒ Object

Returns the modification time of file.



139
140
141
142
143
144
145
146
147
# File 'lib/mizuno/reloader.rb', line 139

def mtime(file)
    begin
        return unless file
        stat = File.stat(file)
        stat.file? ? stat.mtime.to_i : nil
    rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ESRCH
        nil
    end
end

#reload!(force = false) ⇒ Object

Reloads the application if (a) we haven’t reloaded in since our last check, and © some other thread hasn’t handled the update.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/mizuno/reloader.rb', line 57

def reload!(force = false)
    return unless (Time.now.to_i > @threshold)
    @threshold = Time.now.to_i + @interval
    return unless (force or \
        ((timestamp = mtime(@trigger)).to_i > @updated))
    timestamp ||= Time.now.to_i

    # Check updated files to ensure they're loadable.
    missing, errors = 0, 0
    files = find_files_for_reload do |file, file_mtime|
        next(missing += 1 and nil) unless file_mtime
        next unless (file_mtime >= @updated)
        next(errors += 1 and nil) unless verify(file)
        file
    end

    # Cowardly fail if we can't load something.
    @logger.debug("#{missing} files missing during reload.") \
        if (missing > 0)
    return(@logger.error("#{errors} errors, not reloading.")) \
        if (errors > 0)

    # Reload everything that's changed.
    files.each do |file|
        next unless file
        @logger.info("Reloading #{file}")
        load(file) 
    end
    @updated = timestamp
end

#verify(file) ⇒ Object

Returns true if the file is loadable; uses the wrapper functionality of Kernel#load to protect the global namespace.



103
104
105
106
107
108
109
110
111
112
# File 'lib/mizuno/reloader.rb', line 103

def verify(file)
    begin
        @logger.debug("Verifying #{file}")
        load(file, true)
        return(true)
    rescue => error
        @logger.error("Failed to verify #{file}: #{error.to_s}")
        error.backtrace.each { |l| @logger.error("    #{l}") }
    end
end