Improve reliability of EventedFileUpdateCheckerTest fork test
`Listen.to` starts a bunch of background threads that need to perform some work before they are able to receive events, but it doesn't block until they are ready, which expose us to a race condition. With `wait_for_state(:processing_events)` we can ensure that it's ready on Linux, however on macOS, the Darwin backend has a second background thread we can't wait on. As a workaround we wait a bit after the fork to allow that thread to reach it's listning state.
This commit is contained in:
parent
dd722263ff
commit
c650ef4230
@ -45,6 +45,10 @@ def initialize(files, dirs = {}, &block)
|
||||
ObjectSpace.define_finalizer(self, @core.finalizer)
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<ActiveSupport::EventedFileUpdateChecker:#{object_id} @files=#{@core.files.to_a.inspect}"
|
||||
end
|
||||
|
||||
def updated?
|
||||
if @core.restart?
|
||||
@core.thread_safely(&:restart)
|
||||
@ -68,7 +72,7 @@ def execute_if_updated
|
||||
end
|
||||
|
||||
class Core
|
||||
attr_reader :updated
|
||||
attr_reader :updated, :files
|
||||
|
||||
def initialize(files, dirs)
|
||||
@files = files.map { |file| Pathname(file).expand_path }.to_set
|
||||
@ -86,7 +90,11 @@ def initialize(files, dirs)
|
||||
@mutex = Mutex.new
|
||||
|
||||
start
|
||||
@after_fork = ActiveSupport::ForkTracker.after_fork { start }
|
||||
# inotify / FSEvents file descriptors are inherited on fork, so
|
||||
# we need to reopen them otherwise only the parent or the child
|
||||
# will be notified.
|
||||
# FIXME: this callback is keeping a reference on the instance
|
||||
@after_fork = ActiveSupport::ForkTracker.after_fork { restart }
|
||||
end
|
||||
|
||||
def finalizer
|
||||
@ -107,6 +115,11 @@ def start
|
||||
@dtw, @missing = [*@dtw, *@missing].partition(&:exist?)
|
||||
@listener = @dtw.any? ? Listen.to(*@dtw, &method(:changed)) : nil
|
||||
@listener&.start
|
||||
|
||||
# Wait for the listener to be ready to avoid race conditions
|
||||
# Unfortunately this isn't quite enough on macOS because the Darwin backend
|
||||
# has an extra private thread we can't wait on.
|
||||
@listener&.wait_for_state(:processing_events)
|
||||
end
|
||||
|
||||
def stop
|
||||
|
@ -26,7 +26,7 @@ def teardown
|
||||
end
|
||||
|
||||
def wait
|
||||
sleep 1
|
||||
sleep 0.5
|
||||
end
|
||||
|
||||
def mkdir(dirs)
|
||||
@ -60,6 +60,10 @@ def rm_f(files)
|
||||
pid = fork do
|
||||
assert_not_predicate checker, :updated?
|
||||
|
||||
# The listen gem start multiple background threads that need to reach a ready state.
|
||||
# Unfortunately, it doesn't look like there is a clean way to block until they are ready.
|
||||
wait
|
||||
|
||||
# Fork is booted, ready for file to be touched
|
||||
# notify parent process.
|
||||
boot_writer.write("booted")
|
||||
@ -70,7 +74,7 @@ def rm_f(files)
|
||||
|
||||
assert_predicate checker, :updated?
|
||||
rescue Exception => ex
|
||||
result_writer.write(ex.class.name)
|
||||
result_writer.write("#{ex.class.name}: #{ex.message}")
|
||||
raise
|
||||
ensure
|
||||
result_writer.close
|
||||
|
Loading…
Reference in New Issue
Block a user