From 03c7052f78f9106d82760f09003a7a2ed5451ebe Mon Sep 17 00:00:00 2001 From: Cezary Baginski Date: Tue, 3 May 2016 03:09:24 +0200 Subject: [PATCH 1/6] activate JRuby-9.0.5.0 on Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 667cd9be..fbb8d064 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ rvm: - 2.2.4 - jruby-head - rbx-2 + - jruby-9.0.5.0 matrix: allow_failures: - rvm: jruby-head From e86dccf33327477ef67d658a4533313cedb6f594 Mon Sep 17 00:00:00 2001 From: Cezary Baginski Date: Tue, 3 May 2016 03:10:04 +0200 Subject: [PATCH 2/6] avoid unkillable thread problem on JRuby You can't kill a thread that is doing a sysread, so we just send a kill to those threads, and then we ignore them. The allows at least the Travis JRuby tests to run without hanging. --- lib/listen/adapter/linux.rb | 2 ++ lib/listen/internals/thread_pool.rb | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/listen/adapter/linux.rb b/lib/listen/adapter/linux.rb index ec5fe6d1..84f2fc7a 100644 --- a/lib/listen/adapter/linux.rb +++ b/lib/listen/adapter/linux.rb @@ -35,7 +35,9 @@ def _configure(directory, &callback) end def _run + Thread.current[:listen_blocking_read_thread] = true @worker.run + Thread.current[:listen_blocking_read_thread] = false end def _process_event(dir, event) diff --git a/lib/listen/internals/thread_pool.rb b/lib/listen/internals/thread_pool.rb index b1fe7567..e112d90a 100644 --- a/lib/listen/internals/thread_pool.rb +++ b/lib/listen/internals/thread_pool.rb @@ -13,8 +13,16 @@ def self.stop return if @threads.empty? # return to avoid using possibly stubbed Queue killed = Queue.new + # You can't kill a read on a descriptor in JRuby, so let's just + # ignore running threads (listen rb-inotify waiting for disk activity + # before closing) pray threads die faster than they are created... + limit = RUBY_ENGINE == 'jruby' ? [1] : [] + killed << @threads.pop.kill until @threads.empty? - killed.pop.join until killed.empty? + until killed.empty? + th = killed.pop + th.join(*limit) unless th[:listen_blocking_read_thread] + end end end end From c23ff1cb2c43b8d6cc715450d68d45fa41b4b792 Mon Sep 17 00:00:00 2001 From: Cezary Baginski Date: Tue, 3 May 2016 03:12:03 +0200 Subject: [PATCH 3/6] get JRuby to fail with ENOTDIR like MRI does --- lib/listen/directory.rb | 15 +++++++++++++-- lib/listen/record/entry.rb | 13 ++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/listen/directory.rb b/lib/listen/directory.rb index b61db30f..9f9ecaab 100644 --- a/lib/listen/directory.rb +++ b/lib/listen/directory.rb @@ -12,7 +12,7 @@ def self.scan(snapshot, rel_path, options) # TODO: use children(with_directory: false) path = dir + rel_path - current = Set.new(path.children) + current = Set.new(_children(path)) Listen::Logger.debug do format('%s: %s(%s): %s -> %s', @@ -28,7 +28,7 @@ def self.scan(snapshot, rel_path, options) end rescue Errno::ENOENT # The directory changed meanwhile, so rescan it - current = Set.new(path.children) + current = Set.new(_children(path)) retry end @@ -72,5 +72,16 @@ def self._change(snapshot, type, path, options) opts.delete(:recursive) snapshot.invalidate(type, path, opts) end + + def self._children(path) + return path.children unless RUBY_ENGINE == 'jruby' + + # JRuby inconsistency workaround, see: + # https://github.com/jruby/jruby/issues/3840 + exists = path.exist? + directory = path.directory? + return path.children unless (exists && !directory) + raise Errno::ENOTDIR, path.to_s + end end end diff --git a/lib/listen/record/entry.rb b/lib/listen/record/entry.rb index 1fcabb04..cdd47db8 100644 --- a/lib/listen/record/entry.rb +++ b/lib/listen/record/entry.rb @@ -15,7 +15,7 @@ def initialize(root, relative, name = nil) def children child_relative = _join - (Dir.entries(sys_path) - %w(. ..)).map do |name| + (_entries(sys_path) - %w(. ..)).map do |name| Entry.new(@root, child_relative, name) end end @@ -48,6 +48,17 @@ def _join args = [@relative, @name].compact args.empty? ? nil : ::File.join(*args) end + + def _entries(dir) + return Dir.entries(dir) unless RUBY_ENGINE == 'jruby' + + # JRuby inconsistency workaround, see: + # https://github.com/jruby/jruby/issues/3840 + exists = ::File.exist?(dir) + directory = ::File.directory?(dir) + return Dir.entries(dir) unless (exists && !directory) + raise Errno::ENOTDIR, dir + end end end end From 0285e25f1c7612d26f9781d4403f45cf8bbba455 Mon Sep 17 00:00:00 2001 From: Cezary Baginski Date: Tue, 3 May 2016 03:12:22 +0200 Subject: [PATCH 4/6] update gemspec dependencies --- listen.gemspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/listen.gemspec b/listen.gemspec index 6fb0e6a0..3b7cf2cb 100644 --- a/listen.gemspec +++ b/listen.gemspec @@ -30,11 +30,11 @@ Gem::Specification.new do |s| abort "Install 'ruby_dep' gem before building this gem" end - s.add_dependency 'rb-fsevent', '>= 0.9.3' - s.add_dependency 'rb-inotify', '>= 0.9.7' + s.add_dependency 'rb-fsevent', '~> 0.9', '>= 0.9.7' + s.add_dependency 'rb-inotify', '~> 0.9', '>= 0.9.7' # Used to show warnings at runtime - s.add_dependency 'ruby_dep', '~> 1.1' + s.add_dependency 'ruby_dep', '~> 1.2' - s.add_development_dependency 'bundler', '>= 1.3.5' + s.add_development_dependency 'bundler', '~> 1.12' end From c3a8cbb64c24af64dc3b879da5e32caba4ba1906 Mon Sep 17 00:00:00 2001 From: Cezary Baginski Date: Tue, 3 May 2016 03:18:22 +0200 Subject: [PATCH 5/6] fixup! get JRuby to fail with ENOTDIR like MRI does --- lib/listen/directory.rb | 2 +- lib/listen/record/entry.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/listen/directory.rb b/lib/listen/directory.rb index 9f9ecaab..873a7223 100644 --- a/lib/listen/directory.rb +++ b/lib/listen/directory.rb @@ -80,7 +80,7 @@ def self._children(path) # https://github.com/jruby/jruby/issues/3840 exists = path.exist? directory = path.directory? - return path.children unless (exists && !directory) + return path.children unless exists && !directory raise Errno::ENOTDIR, path.to_s end end diff --git a/lib/listen/record/entry.rb b/lib/listen/record/entry.rb index cdd47db8..759be614 100644 --- a/lib/listen/record/entry.rb +++ b/lib/listen/record/entry.rb @@ -56,7 +56,7 @@ def _entries(dir) # https://github.com/jruby/jruby/issues/3840 exists = ::File.exist?(dir) directory = ::File.directory?(dir) - return Dir.entries(dir) unless (exists && !directory) + return Dir.entries(dir) unless exists && !directory raise Errno::ENOTDIR, dir end end From b09a3a7f346196d8c7df04d9b8073e7f31691cd6 Mon Sep 17 00:00:00 2001 From: Cezary Baginski Date: Tue, 3 May 2016 03:54:23 +0200 Subject: [PATCH 6/6] use JRuby workarounds for stubs --- spec/lib/listen/directory_spec.rb | 41 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/spec/lib/listen/directory_spec.rb b/spec/lib/listen/directory_spec.rb index a628c48d..0cfdcaff 100644 --- a/spec/lib/listen/directory_spec.rb +++ b/spec/lib/listen/directory_spec.rb @@ -11,6 +11,16 @@ def fake_dir_stat(name, options = {}) instance_double(::File::Stat, name, defaults.merge(options)) end + def fake_children(ex, dir, *args, &block) + if block_given? + ex.send(:allow, dir).to receive(:children, &block) + else + ex.send(:allow, dir).to receive(:children).and_return(*args) + end + ex.send(:allow, dir).to receive(:exist?).and_return(true) + ex.send(:allow, dir).to receive(:directory?).and_return(true) + end + let(:dir) { double(:dir) } let(:file) { fake_path('file.rb') } let(:file2) { fake_path('file2.rb') } @@ -53,7 +63,7 @@ def fake_dir_stat(name, options = {}) end context 'with empty dir' do - before { allow(dir).to receive(:children) { [] } } + before { fake_children(self, dir, []) } it 'sets record dir path' do expect(record).to receive(:add_dir).with('.') @@ -71,9 +81,8 @@ def fake_dir_stat(name, options = {}) end context 'when subdir is removed' do - before do - allow(dir).to receive(:children) { [file] } - + before do + fake_children(self, dir, [file]) allow(::File).to receive(:lstat).with('file.rb'). and_return(fake_file_stat('file.rb')) end @@ -88,7 +97,7 @@ def fake_dir_stat(name, options = {}) context 'when file.rb removed' do before do - allow(dir).to receive(:children) { [subdir] } + fake_children(self, dir, [subdir]) allow(::File).to receive(:lstat).with('subdir'). and_return(fake_dir_stat('subdir')) @@ -102,7 +111,7 @@ def fake_dir_stat(name, options = {}) context 'when file.rb no longer exists after scan' do before do - allow(dir).to receive(:children).and_return([file], [file2]) + fake_children(self, dir, [file], [file2]) allow(::File).to receive(:lstat).with('file.rb'). and_raise(Errno::ENOENT) @@ -119,7 +128,7 @@ def fake_dir_stat(name, options = {}) context 'when file2.rb is added' do before do - allow(dir).to receive(:children) { [file, file2, subdir] } + fake_children(self, dir, [file, file2, subdir]) allow(::File).to receive(:lstat).with('file.rb'). and_return(fake_file_stat('file.rb')) @@ -142,7 +151,7 @@ def fake_dir_stat(name, options = {}) let(:record_entries) { {} } context 'with non-existing dir path' do - before { allow(dir).to receive(:children) { fail Errno::ENOENT } } + before { fake_children(self, dir) { fail Errno::ENOENT } } it 'reports no changes' do expect(snapshot).to_not receive(:invalidate) @@ -156,7 +165,7 @@ def fake_dir_stat(name, options = {}) end context 'when network share is disconnected' do - before { allow(dir).to receive(:children) { fail Errno::EHOSTDOWN } } + before { fake_children(self, dir) { fail Errno::EHOSTDOWN } } it 'reports no changes' do expect(snapshot).to_not receive(:invalidate) @@ -171,7 +180,7 @@ def fake_dir_stat(name, options = {}) context 'with file.rb in dir' do before do - allow(dir).to receive(:children) { [file] } + fake_children(self, dir, [file]) allow(::File).to receive(:lstat).with('file.rb'). and_return(fake_file_stat('file.rb')) @@ -202,9 +211,7 @@ def fake_dir_stat(name, options = {}) end context 'with empty dir' do - before do - allow(dir).to receive(:children) { [] } - end + before { fake_children(self, dir, []) } it 'snapshots changes for file & subdir path' do expect(snapshot).to receive(:invalidate).with(:file, 'file.rb', {}) @@ -220,7 +227,7 @@ def fake_dir_stat(name, options = {}) let(:subdir2) { fake_path('subdir2', children: []) } before do - allow(dir).to receive(:children) { [subdir2] } + fake_children(self, dir, [subdir2]) allow(subdir2).to receive(:relative_path_from).with(dir) { 'subdir2' } allow(::File).to receive(:lstat).with('subdir2'). @@ -246,7 +253,7 @@ def fake_dir_stat(name, options = {}) context 'with non-existing dir' do before do - allow(dir).to receive(:children) { fail Errno::ENOENT } + fake_children(self, dir) { fail Errno::ENOENT } end it 'reports no changes' do @@ -257,8 +264,8 @@ def fake_dir_stat(name, options = {}) context 'with subdir present in dir' do before do - allow(dir).to receive(:children) { [subdir] } - allow(subdir).to receive(:children) { [] } + fake_children(self, dir, [subdir]) + fake_children(self, subdir, []) allow(::File).to receive(:lstat).with('subdir'). and_return(fake_dir_stat('subdir')) end