Class: AutoReloader

Inherits:
Object
  • Object
show all
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.3.0'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAutoReloader

Returns a new instance of AutoReloader.



32
33
34
# File 'lib/auto_reloader.rb', line 32

def initialize
  @activate_lock = Mutex.new
end

Instance Attribute Details

#default_delayObject (readonly)

Returns the value of attribute default_delay.



15
16
17
# File 'lib/auto_reloader.rb', line 15

def default_delay
  @default_delay
end

#default_onchangeObject (readonly)

Returns the value of attribute default_onchange.



15
16
17
# File 'lib/auto_reloader.rb', line 15

def default_onchange
  @default_onchange
end

#reloadable_pathsObject

Returns the value of attribute reloadable_paths.



15
16
17
# File 'lib/auto_reloader.rb', line 15

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) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/auto_reloader.rb', line 37

def activate(reloadable_paths: [], onchange: true, delay: nil, watch_paths: nil,
            watch_latency: 1, sync_require: false)
  @activate_lock.synchronize do
    raise ActivatedMoreThanOnce, 'Can only activate Autoreloader once' if @reloadable_paths
    @default_delay = delay
    @default_onchange = onchange
    @watch_latency = watch_latency
    sync_require! if sync_require
    @reload_lock = Mutex.new
    @top_level_consts_stack = []
    @unload_constants = Set.new
    @unload_files = Set.new
    @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.



71
72
73
# File 'lib/auto_reloader.rb', line 71

def async_require!
  @require_lock = nil
end

#force_next_reloadObject



151
152
153
# File 'lib/auto_reloader.rb', line 151

def force_next_reload
  @force_reload = true
end

#maybe_synchronize(&block) ⇒ Object



112
113
114
# File 'lib/auto_reloader.rb', line 112

def maybe_synchronize(&block)
  @require_lock ? @require_lock.synchronize(&block) : yield
end

#reload!(delay: default_delay, onchange: default_onchange, watch_paths: @watch_paths) ⇒ Object



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

def reload!(delay: default_delay, onchange: default_onchange, watch_paths: @watch_paths)
  if onchange && !block_given?
    raise InvalidUsage, 'A block must be provided to reload! when onchange is true (the default)'
  end

  unload! unless reload_ignored = ignore_reload?(delay, onchange, watch_paths)

  result = nil
  if block_given?
    result = yield
    find_mtime
  end
  @last_reloaded = clock_time if delay
  result
end

#require(path, &block) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/auto_reloader.rb', line 80

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



116
117
118
# File 'lib/auto_reloader.rb', line 116

def require_relative(path, fullpath)
  require(fullpath){ Kernel.require fullpath }
end

#stop_listenerObject



147
148
149
# File 'lib/auto_reloader.rb', line 147

def stop_listener
  @listener.stop if @listener
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.



62
63
64
# File 'lib/auto_reloader.rb', line 62

def sync_require!
  @require_lock ||= Monitor.new # monitor is like Mutex, but reentrant
end

#unload!Object



137
138
139
140
141
142
143
144
145
# File 'lib/auto_reloader.rb', line 137

def unload!
  @force_reload = false
  @reload_lock.synchronize do
    @unload_files.each{|f| $LOADED_FEATURES.delete f }
    @unload_constants.each{|c| safe_remove_constant c }
    @unload_files = Set.new
    @unload_constants = Set.new
  end
end