auto_reloader 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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