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 +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
|