Class: AutoReloader
- Inherits:
-
Object
- Object
- AutoReloader
- Extended by:
- SingleForwardable
- Includes:
- Singleton
- Defined in:
- lib/auto_reloader.rb,
lib/auto_reloader/version.rb
Defined Under Namespace
Modules: RequireOverride
Constant Summary collapse
- ActivatedMoreThanOnce =
Class.new RuntimeError
- InvalidUsage =
Class.new RuntimeError
- VERSION =
'0.5.0'
Instance Attribute Summary collapse
-
#default_await_before_unload ⇒ Object
readonly
default_await_before_unload will await for all calls to reload! to finish before calling unload!.
-
#default_delay ⇒ Object
readonly
default_await_before_unload will await for all calls to reload! to finish before calling unload!.
-
#default_onchange ⇒ Object
readonly
default_await_before_unload will await for all calls to reload! to finish before calling unload!.
-
#reloadable_paths ⇒ Object
default_await_before_unload will await for all calls to reload! to finish before calling unload!.
Instance Method Summary collapse
- #activate(reloadable_paths: [], onchange: true, delay: nil, watch_paths: nil, watch_latency: 1, sync_require: false, await_before_unload: true) ⇒ Object
-
#async_require! ⇒ Object
See the documentation for sync_require! to understand the reasoning.
- #force_next_reload ⇒ Object
-
#initialize ⇒ AutoReloader
constructor
A new instance of AutoReloader.
- #maybe_synchronize(&block) ⇒ Object
- #register_unload_hook(&hook) ⇒ Object
- #reload!(delay: default_delay, onchange: default_onchange, watch_paths: @watch_paths, await_before_unload: default_await_before_unload) ⇒ Object
- #require(path, &block) ⇒ Object
- #require_relative(path, fullpath) ⇒ Object
- #run_unload_hook(hook) ⇒ Object
- #stop_listener ⇒ Object
-
#sync_require! ⇒ Object
when concurrent threads require files race conditions may prevent the automatic detection of constants created by a given file.
- #unload! ⇒ Object
Constructor Details
#initialize ⇒ AutoReloader
Returns a new instance of AutoReloader.
37 38 39 |
# File 'lib/auto_reloader.rb', line 37 def initialize @activate_lock = Mutex.new end |
Instance Attribute Details
#default_await_before_unload ⇒ Object (readonly)
default_await_before_unload will await for all calls to reload! to finish before calling unload!. This behavior is usually desired in web applications to avoid unloading anything while a request hasn’t been finished, however it won’t work fine if some requests are supposed to remain open, like websockets connections or something like that.
20 21 22 |
# File 'lib/auto_reloader.rb', line 20 def default_await_before_unload @default_await_before_unload end |
#default_delay ⇒ Object (readonly)
default_await_before_unload will await for all calls to reload! to finish before calling unload!. This behavior is usually desired in web applications to avoid unloading anything while a request hasn’t been finished, however it won’t work fine if some requests are supposed to remain open, like websockets connections or something like that.
20 21 22 |
# File 'lib/auto_reloader.rb', line 20 def default_delay @default_delay end |
#default_onchange ⇒ Object (readonly)
default_await_before_unload will await for all calls to reload! to finish before calling unload!. This behavior is usually desired in web applications to avoid unloading anything while a request hasn’t been finished, however it won’t work fine if some requests are supposed to remain open, like websockets connections or something like that.
20 21 22 |
# File 'lib/auto_reloader.rb', line 20 def default_onchange @default_onchange end |
#reloadable_paths ⇒ Object
default_await_before_unload will await for all calls to reload! to finish before calling unload!. This behavior is usually desired in web applications to avoid unloading anything while a request hasn’t been finished, however it won’t work fine if some requests are supposed to remain open, like websockets connections or something like that.
20 21 22 |
# File 'lib/auto_reloader.rb', line 20 def reloadable_paths @reloadable_paths end |
Instance Method Details
#activate(reloadable_paths: [], onchange: true, delay: nil, watch_paths: nil, watch_latency: 1, sync_require: false, await_before_unload: true) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/auto_reloader.rb', line 42 def activate(reloadable_paths: [], onchange: true, delay: nil, watch_paths: nil, watch_latency: 1, sync_require: false, await_before_unload: true) @activate_lock.synchronize do raise ActivatedMoreThanOnce, 'Can only activate Autoreloader once' if @reloadable_paths @default_delay = delay @default_onchange = onchange @default_await_before_unload = await_before_unload @watch_latency = watch_latency sync_require! if sync_require @reload_lock = Mutex.new @zero_requests_condition = ConditionVariable.new @requests_count = 0 @top_level_consts_stack = [] @unload_constants = Set.new @unload_files = Set.new @unload_hooks = [] @last_reloaded = clock_time try_listen unless watch_paths == false self.reloadable_paths = reloadable_paths Object.include RequireOverride end end |
#async_require! ⇒ Object
See the documentation for sync_require! to understand the reasoning. Async require is the default behavior but could lead to race conditions. If you know your requires will never block it may be a good idea to call sync_require!. If you know what require will block you can call async_require!, require it, and then call sync_require! which will generate a new monitor.
80 81 82 |
# File 'lib/auto_reloader.rb', line 80 def async_require! @require_lock = nil end |
#force_next_reload ⇒ Object
192 193 194 |
# File 'lib/auto_reloader.rb', line 192 def force_next_reload @force_reload = true end |
#maybe_synchronize(&block) ⇒ Object
121 122 123 |
# File 'lib/auto_reloader.rb', line 121 def maybe_synchronize(&block) @require_lock ? @require_lock.synchronize(&block) : yield end |
#register_unload_hook(&hook) ⇒ Object
172 173 174 175 176 177 |
# File 'lib/auto_reloader.rb', line 172 def register_unload_hook(&hook) raise InvalidUsage, "A block is required for register_unload_hook" unless block_given? @reload_lock.synchronize do @unload_hooks << hook end end |
#reload!(delay: default_delay, onchange: default_onchange, watch_paths: @watch_paths, await_before_unload: default_await_before_unload) ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/auto_reloader.rb', line 130 def reload!(delay: default_delay, onchange: default_onchange, watch_paths: @watch_paths, await_before_unload: default_await_before_unload) if onchange && !block_given? raise InvalidUsage, 'A block must be provided to reload! when onchange is true (the default)' end unless reload_ignored = ignore_reload?(delay, onchange, watch_paths) @reload_lock.synchronize do @zero_requests_condition.wait(@reload_lock) unless @requests_count == 0 end if await_before_unload && block_given? unload! end result = nil if block_given? @reload_lock.synchronize{ @requests_count += 1 } begin result = yield !reload_ignored ensure @reload_lock.synchronize{ @requests_count -= 1 @zero_requests_condition.signal if @requests_count == 0 } end find_mtime end @last_reloaded = clock_time if delay result end |
#require(path, &block) ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/auto_reloader.rb', line 89 def require(path, &block) was_required = false error = nil maybe_synchronize do @top_level_consts_stack << Set.new old_consts = Object.constants prev_consts = new_top_level_constants = nil begin was_required = yield rescue Exception => e error = e ensure prev_consts = @top_level_consts_stack.pop return false if !error && !was_required # was required already, do nothing new_top_level_constants = Object.constants - old_consts - prev_consts.to_a (new_top_level_constants.each{|c| safe_remove_constant c }; raise error) if error @top_level_consts_stack.each{|c| c.merge new_top_level_constants } full_loaded_path = $LOADED_FEATURES.last return was_required unless reloadable? full_loaded_path, path @reload_lock.synchronize do @unload_constants.merge new_top_level_constants @unload_files << full_loaded_path end end end was_required end |
#require_relative(path, fullpath) ⇒ Object
125 126 127 |
# File 'lib/auto_reloader.rb', line 125 def require_relative(path, fullpath) require(fullpath){ Kernel.require fullpath } end |
#run_unload_hook(hook) ⇒ Object
179 180 181 182 183 |
# File 'lib/auto_reloader.rb', line 179 def run_unload_hook(hook) hook.call rescue => e puts "Failed to run unload hook in AutoReloader: #{e.message}.\n\n#{e.backtrace.join("\n")}" end |
#stop_listener ⇒ Object
185 186 187 188 189 190 |
# File 'lib/auto_reloader.rb', line 185 def stop_listener @reload_lock.synchronize do @listener.stop if @listener @listener = nil end end |
#sync_require! ⇒ Object
when concurrent threads require files race conditions may prevent the automatic detection of constants created by a given file. Calling sync_require! will ensure only a single file is required at a single time. However, if a required file blocks (think of a web server) then any requires by a separate thread would be blocked forever (or until the web server shutdowns). That’s why require is async by default even though it would be vulnerable to race conditions.
71 72 73 |
# File 'lib/auto_reloader.rb', line 71 def sync_require! @require_lock ||= Monitor.new # monitor is like Mutex, but reentrant end |
#unload! ⇒ Object
160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/auto_reloader.rb', line 160 def unload! @force_reload = false @reload_lock.synchronize do @unload_hooks.reverse_each{|h| run_unload_hook h } @unload_files.each{|f| $LOADED_FEATURES.delete f } @unload_constants.each{|c| safe_remove_constant c } @unload_hooks = [] @unload_files = Set.new @unload_constants = Set.new end end |