Skip to content

Commit

Permalink
Merge pull request #396 from guard/backport_major_bugfixes
Browse files Browse the repository at this point in the history
Backport various bugfixes up to Listen 3.1.5
  • Loading branch information
e2 committed May 18, 2016
2 parents 432e9da + 7f89a65 commit acd9de4
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 26 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -16,6 +16,7 @@ matrix:
os:
- linux
- osx
osx_image: xcode7.1
env:
# TODO: 0.8 is enough on Linux, but 2 seems needed for Travis/OSX
- LISTEN_TESTS_DEFAULT_LAG=2
Expand Down
23 changes: 20 additions & 3 deletions lib/listen/adapter/darwin.rb
Expand Up @@ -6,17 +6,34 @@ module Adapter
# Adapter implementation for Mac OS X `FSEvents`.
#
class Darwin < Base
OS_REGEXP = /darwin(1.+)?$/i
OS_REGEXP = /darwin(?<major_version>1\d+)/i

# The default delay between checking for changes.
DEFAULTS = { latency: 0.1 }

INCOMPATIBLE_GEM_VERSION = <<-EOS.gsub(/^ {8}/, '')
rb-fsevent > 0.9.4 no longer supports OS X 10.6 through 10.8.
Please add the following to your Gemfile to avoid polling for changes:
require 'rbconfig'
if RbConfig::CONFIG['target_os'] =~ /darwin(1[0-3])/i
gem 'rb-fsevent', '<= 0.9.4'
end
EOS

def self.usable?
require 'rb-fsevent'
darwin_version = RbConfig::CONFIG['target_os'][OS_REGEXP, :major_version] or return false
return true if darwin_version.to_i >= 13 # darwin13 is OS X 10.9
return true if Gem::Version.new(FSEvent::VERSION) <= Gem::Version.new('0.9.4')
Kernel.warn INCOMPATIBLE_GEM_VERSION
false
end

private

# NOTE: each directory gets a DIFFERENT callback!
def _configure(dir, &callback)
require 'rb-fsevent'

opts = { latency: options.latency }

@workers ||= ::Queue.new
Expand Down
4 changes: 3 additions & 1 deletion lib/listen/adapter/linux.rb
Expand Up @@ -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)
Expand Down Expand Up @@ -99,7 +101,7 @@ def _dir_event?(event)
end

def _stop
@worker.close
@worker && @worker.close
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions lib/listen/backend.rb
Expand Up @@ -2,6 +2,8 @@
require 'listen/adapter/base'
require 'listen/adapter/config'

require 'forwardable'

# This class just aggregates configuration object to avoid Listener specs
# from exploding with huge test setup blocks
module Listen
Expand Down
15 changes: 13 additions & 2 deletions lib/listen/directory.rb
Expand Up @@ -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',
Expand All @@ -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

Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions lib/listen/event/queue.rb
@@ -1,5 +1,7 @@
require 'thread'

require 'forwardable'

module Listen
module Event
class Queue
Expand Down
10 changes: 9 additions & 1 deletion lib/listen/internals/thread_pool.rb
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/listen/listener.rb
Expand Up @@ -61,7 +61,7 @@ def initialize(*dirs, &block)

default_state :initializing

state :initializing, to: :backend_started
state :initializing, to: [:backend_started, :stopped]

state :backend_started, to: [:frontend_ready, :stopped] do
backend.start
Expand Down
13 changes: 12 additions & 1 deletion lib/listen/record/entry.rb
Expand Up @@ -13,7 +13,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
Expand Down Expand Up @@ -46,6 +46,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
4 changes: 2 additions & 2 deletions listen.gemspec
Expand Up @@ -24,8 +24,8 @@ Gem::Specification.new do |s|

s.required_ruby_version = '>= 1.9.3'

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.4'
s.add_dependency 'rb-inotify', '~> 0.9', '>= 0.9.7'

s.add_development_dependency 'bundler', '>= 1.3.5'
end
39 changes: 26 additions & 13 deletions spec/lib/listen/adapter/linux_spec.rb
Expand Up @@ -131,24 +131,37 @@
let(:adapter_options) { { events: [:recursive, :close_write] } }

before do
events = [:recursive, :close_write]
allow(fake_worker).to receive(:watch).with('/foo/dir1', *events)

fake_notifier = double(:fake_notifier, new: fake_worker)
stub_const('INotify::Notifier', fake_notifier)

allow(config).to receive(:directories).and_return(directories)
allow(config).to receive(:adapter_options).and_return(adapter_options)
allow(config).to receive(:queue).and_return(queue)
allow(config).to receive(:silencer).and_return(silencer)
end

allow(subject).to receive(:require).with('rb-inotify')
subject.configure
context 'when configured' do
before do
events = [:recursive, :close_write]
allow(fake_worker).to receive(:watch).with('/foo/dir1', *events)

fake_notifier = double(:fake_notifier, new: fake_worker)
stub_const('INotify::Notifier', fake_notifier)

allow(config).to receive(:queue).and_return(queue)
allow(config).to receive(:silencer).and_return(silencer)

allow(subject).to receive(:require).with('rb-inotify')
subject.configure
end

it 'stops the worker' do
expect(fake_worker).to receive(:close)
subject.stop
end
end

it 'stops the worker' do
expect(fake_worker).to receive(:close)
subject.stop
context 'when not even initialized' do
it 'does not crash' do
expect do
subject.stop
end.to_not raise_error
end
end
end
end
Expand Down
16 changes: 14 additions & 2 deletions spec/lib/listen/listener_spec.rb
Expand Up @@ -85,8 +85,8 @@

context 'with a block' do
let(:myblock) { instance_double(Proc) }
let(:block) { proc { myblock.call } }
subject { described_class.new('dir1', &block) }
let(:real_block) { proc { myblock.call } }
subject { described_class.new('dir1', &real_block) }

it 'passes the block to the event processor' do
allow(Event::Config).to receive(:new) do |*_args, &some_block|
Expand Down Expand Up @@ -175,6 +175,18 @@
subject.stop
end
end

context 'when only initialized' do
before do
subject
end

it 'terminates' do
allow(backend).to receive(:stop)
allow(processor).to receive(:teardown)
subject.stop
end
end
end

describe '#pause' do
Expand Down

0 comments on commit acd9de4

Please sign in to comment.