auto_reloader 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe07852820635d96113ac46d70341b88a25bc229
4
- data.tar.gz: c269465db561170e62fbc72edfb39e056550b8dd
3
+ metadata.gz: b42889c4bcc15ca1ea692a102bcb218407d0c035
4
+ data.tar.gz: 691c8f940b95e40ecb067b51d86bcd7f479589d0
5
5
  SHA512:
6
- metadata.gz: 2a4fabc3d94d9f355d523635cfa560413e487c05cee3422739ce353cbdc68bb1a6913a2cba5ab7e919c0d3b158fdf992f911d4fe850d8dcfa54e8c6d2d27a919
7
- data.tar.gz: 407453e32124a922ae721c3cd543f13e335fe2e39b892f2b8888a40988b258d8a8fe8a95135d936580b0eb736af8d5080a6f61c1ef76de256766426eb17ee01c
6
+ metadata.gz: d5bfbc0440e93e598cd4c433564e570bdfcfc7b0d9e37aa7aad9bf14135b5ba0b25a9a1b2478729a937654373a736701428c654125bb9e6e5d51a3f2c8de9130
7
+ data.tar.gz: 23ba39d8a90edb4b96da5facb262bba884f02a86aa6be21271230e24d580c9735ec78444dd13fb99360da88265ca119010821dfbda62874f9d98fb7a09db2e82
data/README.md CHANGED
@@ -77,6 +77,27 @@ Listen.to(File.expand_path('config', __dir__)) do |added, modified, removed|
77
77
  end
78
78
  ```
79
79
 
80
+ ## Thread-safety
81
+
82
+ In order for the automatic constants and required files detection to work correctly it should
83
+ process a single require at a time. If your code has multiple threads requiring code, then it
84
+ might cause a race condition that could cause unexpected bugs to happen in AutoReloader. This
85
+ is the default behavior because it's not common to call require from multiple threads in the
86
+ development environment but adding a monitor around require could create a dead-lock which is
87
+ a more serious issue.
88
+
89
+ For example, if requiring a file would start a web server and block, if the web server is
90
+ started in a separate thread (which could be joined so that the require doesn't return), then
91
+ it wouldn't be able to require new files because the lock was acquired by another thread and
92
+ won't be released while the web server is running.
93
+
94
+ If you are sure that no require should block in your application (which is also common), you're
95
+ encouraged to call `AutoReloader.sync_require!`. Or pass `sync_require: true` to
96
+ `AutoReloader.activate`. You may even control this behavior dynamically so that you call
97
+ `AutoReloader.async_require!` before the blocking require and then reenable the sync behavior.
98
+ The sync behavior will ensure no race conditions that would break the automatic detection
99
+ mechanism would ever happen.
100
+
80
101
  ## Known Caveats
81
102
 
82
103
  In order to work transparently AutoReloader will override `require` and `require_relative` when
@@ -1,3 +1,3 @@
1
1
  class AutoReloader
2
- VERSION = '0.2.6'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/auto_reloader.rb CHANGED
@@ -15,7 +15,7 @@ class AutoReloader
15
15
  attr_reader :reloadable_paths, :default_onchange, :default_delay
16
16
 
17
17
  def_delegators :instance, :activate, :reload!, :reloadable_paths, :reloadable_paths=,
18
- :unload!, :force_next_reload
18
+ :unload!, :force_next_reload, :sync_require!, :async_require!
19
19
 
20
20
  module RequireOverride
21
21
  def require(path)
@@ -35,13 +35,13 @@ class AutoReloader
35
35
 
36
36
  ActivatedMoreThanOnce = Class.new RuntimeError
37
37
  def activate(reloadable_paths: [], onchange: true, delay: nil, watch_paths: nil,
38
- watch_latency: 1)
38
+ watch_latency: 1, sync_require: false)
39
39
  @activate_lock.synchronize do
40
40
  raise ActivatedMoreThanOnce, 'Can only activate Autoreloader once' if @reloadable_paths
41
41
  @default_delay = delay
42
42
  @default_onchange = onchange
43
43
  @watch_latency = watch_latency
44
- @require_lock = Monitor.new # monitor is like Mutex, but reentrant
44
+ sync_require! if sync_require
45
45
  @reload_lock = Mutex.new
46
46
  @top_level_consts_stack = []
47
47
  @unload_constants = Set.new
@@ -53,6 +53,25 @@ class AutoReloader
53
53
  end
54
54
  end
55
55
 
56
+ # when concurrent threads require files race conditions may prevent the automatic detection
57
+ # of constants created by a given file. Calling sync_require! will ensure only a single file
58
+ # is required at a single time. However, if a required file blocks (think of a web server)
59
+ # then any requires by a separate thread would be blocked forever (or until the web server
60
+ # shutdowns). That's why require is async by default even though it would be vulnerable to
61
+ # race conditions.
62
+ def sync_require!
63
+ @require_lock ||= Monitor.new # monitor is like Mutex, but reentrant
64
+ end
65
+
66
+ # See the documentation for sync_require! to understand the reasoning. Async require is the
67
+ # default behavior but could lead to race conditions. If you know your requires will never
68
+ # block it may be a good idea to call sync_require!. If you know what require will block you
69
+ # can call async_require!, require it, and then call sync_require! which will generate a new
70
+ # monitor.
71
+ def async_require!
72
+ @require_lock = nil
73
+ end
74
+
56
75
  def reloadable_paths=(paths)
57
76
  @reloadable_paths = paths.map{|rp| File.expand_path(rp).freeze }.freeze
58
77
  setup_listener if @watch_paths
@@ -61,7 +80,7 @@ class AutoReloader
61
80
  def require(path, &block)
62
81
  was_required = false
63
82
  error = nil
64
- @require_lock.synchronize do
83
+ maybe_synchronize do
65
84
  @top_level_consts_stack << Set.new
66
85
  old_consts = Object.constants
67
86
  prev_consts = new_top_level_constants = nil
@@ -90,6 +109,10 @@ class AutoReloader
90
109
  was_required
91
110
  end
92
111
 
112
+ def maybe_synchronize(&block)
113
+ @require_lock ? @require_lock.synchronize(&block) : yield
114
+ end
115
+
93
116
  def require_relative(path, fullpath)
94
117
  require(fullpath){ Kernel.require fullpath }
95
118
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auto_reloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Rosenfeld Rosas