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 +4 -4
- data/README.md +21 -0
- data/lib/auto_reloader/version.rb +1 -1
- data/lib/auto_reloader.rb +27 -4
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b42889c4bcc15ca1ea692a102bcb218407d0c035
|
4
|
+
data.tar.gz: 691c8f940b95e40ecb067b51d86bcd7f479589d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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
|
-
|
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
|