mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #25302 from schneems/schneems/evented-file-boot-at-check-time-master
EventedFileUpdateChecker boots once per process
This commit is contained in:
commit
30dd8b2cb0
2 changed files with 87 additions and 6 deletions
|
@ -3,6 +3,33 @@ require 'pathname'
|
|||
require 'concurrent/atomic/atomic_boolean'
|
||||
|
||||
module ActiveSupport
|
||||
# Allows you to "listen" to changes in a file system.
|
||||
# The evented file updater does not hit disk when checking for updates
|
||||
# instead it uses platform specific file system events to trigger a change
|
||||
# in state.
|
||||
#
|
||||
# The file checker takes an array of files to watch or a hash specifying directories
|
||||
# and file extensions to watch. It also takes a block that is called when
|
||||
# EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated
|
||||
# is run and there have been changes to the file system.
|
||||
#
|
||||
# Note: Forking will cause the first call to `updated?` to return `true`.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# checker = EventedFileUpdateChecker.new(["/tmp/foo"], -> { puts "changed" })
|
||||
# checker.updated?
|
||||
# # => false
|
||||
# checker.execute_if_updated
|
||||
# # => nil
|
||||
#
|
||||
# FileUtils.touch("/tmp/foo")
|
||||
#
|
||||
# checker.updated?
|
||||
# # => true
|
||||
# checker.execute_if_updated
|
||||
# # => "changed"
|
||||
#
|
||||
class EventedFileUpdateChecker #:nodoc: all
|
||||
def initialize(files, dirs = {}, &block)
|
||||
@ph = PathHelper.new
|
||||
|
@ -13,11 +40,13 @@ module ActiveSupport
|
|||
@dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
|
||||
end
|
||||
|
||||
@block = block
|
||||
@updated = Concurrent::AtomicBoolean.new(false)
|
||||
@lcsp = @ph.longest_common_subpath(@dirs.keys)
|
||||
@block = block
|
||||
@updated = Concurrent::AtomicBoolean.new(false)
|
||||
@lcsp = @ph.longest_common_subpath(@dirs.keys)
|
||||
@pid = Process.pid
|
||||
@boot_mutex = Mutex.new
|
||||
|
||||
if (dtw = directories_to_watch).any?
|
||||
if (@dtw = directories_to_watch).any?
|
||||
# Loading listen triggers warnings. These are originated by a legit
|
||||
# usage of attr_* macros for private attributes, but adds a lot of noise
|
||||
# to our test suite. Thus, we lazy load it and disable warnings locally.
|
||||
|
@ -28,11 +57,18 @@ module ActiveSupport
|
|||
raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
|
||||
end
|
||||
end
|
||||
Listen.to(*dtw, &method(:changed)).start
|
||||
end
|
||||
boot!
|
||||
end
|
||||
|
||||
def updated?
|
||||
@boot_mutex.synchronize do
|
||||
if @pid != Process.pid
|
||||
boot!
|
||||
@pid = Process.pid
|
||||
@updated.make_true
|
||||
end
|
||||
end
|
||||
@updated.true?
|
||||
end
|
||||
|
||||
|
@ -50,6 +86,9 @@ module ActiveSupport
|
|||
end
|
||||
|
||||
private
|
||||
def boot!
|
||||
Listen.to(*@dtw, &method(:changed)).start
|
||||
end
|
||||
|
||||
def changed(modified, added, removed)
|
||||
unless updated?
|
||||
|
|
|
@ -11,7 +11,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def new_checker(files = [], dirs = {}, &block)
|
||||
ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do
|
||||
ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do |c|
|
||||
wait
|
||||
end
|
||||
end
|
||||
|
@ -34,6 +34,48 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
|
|||
super
|
||||
wait
|
||||
end
|
||||
|
||||
test 'notifies forked processes' do
|
||||
FileUtils.touch(tmpfiles)
|
||||
|
||||
checker = new_checker(tmpfiles) { }
|
||||
assert !checker.updated?
|
||||
|
||||
# Pipes used for flow controll across fork.
|
||||
boot_reader, boot_writer = IO.pipe
|
||||
touch_reader, touch_writer = IO.pipe
|
||||
|
||||
pid = fork do
|
||||
assert checker.updated?
|
||||
|
||||
# Clear previous check value.
|
||||
checker.execute
|
||||
assert !checker.updated?
|
||||
|
||||
# Fork is booted, ready for file to be touched
|
||||
# notify parent process.
|
||||
boot_writer.write("booted")
|
||||
|
||||
# Wait for parent process to signal that file
|
||||
# has been touched.
|
||||
IO.select([touch_reader])
|
||||
|
||||
assert checker.updated?
|
||||
end
|
||||
|
||||
assert pid
|
||||
|
||||
# Wait for fork to be booted before touching files.
|
||||
IO.select([boot_reader])
|
||||
touch(tmpfiles)
|
||||
|
||||
# Notify fork that files have been touched.
|
||||
touch_writer.write("touched")
|
||||
|
||||
assert checker.updated?
|
||||
|
||||
Process.wait(pid)
|
||||
end
|
||||
end
|
||||
|
||||
class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase
|
||||
|
|
Loading…
Reference in a new issue