listen 2.7.11 → 2.7.12

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.
data/Rakefile CHANGED
@@ -2,4 +2,11 @@ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
- task default: :spec
5
+
6
+ if ENV["CI"] != "true"
7
+ require "rubocop/rake_task"
8
+ RuboCop::RakeTask.new(:rubocop)
9
+ task default: [:spec, :rubocop]
10
+ else
11
+ task default: [:spec]
12
+ end
@@ -0,0 +1,139 @@
1
+ # Issues and troubleshooting
2
+
3
+ ## 3 steps before you start diagnosing problems
4
+
5
+ These 3 steps will:
6
+ * help quickly troubleshoot issues caused by obscure problems
7
+ * help quickly identify the area of the problem (a full list is [below](#known-issues))
8
+ * help you get familiar with listen's diagnostic mode
9
+ * help you create relevant output before you submit an issue
10
+
11
+ 1) For effective troubleshooting set the `LISTEN_GEM_DEBUGGING=1` variable
12
+ before starting listen.
13
+
14
+ 2) Verify polling works (see `force_polling` option).
15
+
16
+ After starting listen, you should see something like:
17
+ ```
18
+ INFO -- : Celluloid loglevel set to: 1
19
+ INFO -- : Record.build(): 0.06773114204406738 seconds
20
+ ```
21
+
22
+ (Listen uses [Celluloid](https://github.com/celluloid/celluloid) for logging, so if you don't see anything, `Celluloid.logger` might have been disabled by a different gem, e.g. sidekiq)
23
+
24
+ If you don't see the line `Record.build()`:
25
+ * and there's a lot of disk activity, you may have to wait a few seconds
26
+ * you may be using an outdated version of Listen
27
+ * listen may have got stuck on a recursive symlink, see #259
28
+
29
+ 3) Make changes e.g. `touch foo` or `echo "a" >> foo` (for troubleshooting, avoid using an editor which could generate too many misleading events)
30
+
31
+ You should see something like:
32
+
33
+ ```
34
+ INFO -- : listen: raw changes: [[:added, "/home/me/foo"]]
35
+ INFO -- : listen: final changes: {:modified=>[], :added=>["/home/me/foo"], :removed=>[]}
36
+ ```
37
+
38
+ "raw changes" contains changes collected during the `:wait_for_delay` and `:latency` intervals, while "final changes" is what listen decided are relevant changes (for better editor support).
39
+
40
+ ## Adapter-specific diagnostics
41
+
42
+ Use the `LISTEN_GEM_DEBUGGING` set to `2` for additional info.
43
+
44
+ E.g. you'll get:
45
+
46
+ ```
47
+ INFO -- : Celluloid loglevel set to: 0
48
+ DEBUG -- : Broadcaster: starting tcp server: 127.0.0.1:4000
49
+ DEBUG -- : Adapter: considering TCP ...
50
+ DEBUG -- : Adapter: considering polling ...
51
+ DEBUG -- : Adapter: considering optimized backend...
52
+ INFO -- : Record.build(): 0.0007264614105224609 seconds
53
+ DEBUG -- : inotify: foo ([:create])
54
+ DEBUG -- : raw queue: [:file, #<Pathname:/tmp/x>, "foo", {:change=>:added}]
55
+ DEBUG -- : added: file:/tmp/x/foo ({:change=>:added})
56
+ DEBUG -- : inotify: foo ([:attrib])
57
+ DEBUG -- : raw queue: [:file, #<Pathname:/tmp/x>, "foo", {:change=>:modified}]
58
+ DEBUG -- : inotify: foo ([:close_write, :close])
59
+ DEBUG -- : raw queue: [:file, #<Pathname:/tmp/x>, "foo", {:change=>:modified}]
60
+ DEBUG -- : modified: file:/tmp/x/foo ({:change=>:modified})
61
+ DEBUG -- : modified: file:/tmp/x/foo ({:change=>:modified})
62
+ INFO -- : listen: raw changes: [[:added, "/tmp/x/foo"]]
63
+ INFO -- : listen: final changes: {:modified=>[], :added=>["/tmp/x/foo"], :removed=>[]}
64
+ DEBUG -- : Callback took 4.410743713378906e-05 seconds
65
+ ```
66
+
67
+ This shows:
68
+ * host port listened to (for forwarding events)
69
+ * the actual adapter used (here, it's "optimized backend")
70
+ * the event received (here it's `:create` from rb-inotify)
71
+ * "raw queue" - events queued for processing (collected during `:latency`)
72
+ * "Callback took" - how long it took your app to process changes
73
+
74
+ #### Known issues
75
+
76
+ Here are common issues grouped by area in which they occur:
77
+
78
+ 1. System/OS
79
+ * [Update your Dropbox client](http://www.dropbox.com/downloading), if you have Dropbox installed.
80
+ * old MacOS (< 10.6)
81
+ * certain old versions of Ruby (try a newer Ruby on Windows for `wdm` and TCP mode to work)
82
+ * system limits
83
+ * threads for Celluloid (e.g. Virtual Machine CPU/RAM limitations)
84
+ * [inotify limits (Linux)](https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers)
85
+ * system in an inconsistent state or stuck (try rebooting/updating on Windows/Mac - seriously!)
86
+ * FSEvent bug: (http://feedback.livereload.com/knowledgebase/articles/86239)
87
+
88
+ 2. Installation/gems/config
89
+ * not running listen or your app (e.g. guard) with `bundle exec` first
90
+ * old version of listen
91
+ * problems with adapter gems (`wdm`, `rb-fsevent`, `rb-inotify`) not installed, not detected properly (Windows) or not in Gemfile (Windows)
92
+ * Celluloid actors are silently crashing (when no LISTEN_GEM_DEBUGGING variable is present)
93
+ * see the [Performance](https://github.com/guard/listen/blob/master/README.md#Performance) section in the README
94
+
95
+ 3. Filesystem
96
+ * VM shared folders and network folders (NFS, Samba, SSHFS) don't work with optimized backends (workaround: polling, [TCP mode](https://github.com/guard/listen/blob/master/README.md#forwarding-file-events-over-tcp), Vagrant's rsync-auto mode, rsync/unison)
97
+ * FAT/HFS timestamps have 1-second precision, which can cause polling and rb-fsevent to be very slow on large files (try `LISTEN_GEM_DISABLE_HASHING` variable)
98
+ * virtual filesystems may not implement event monitoring
99
+ * restrictive file/folder permissions
100
+ * watched folders moved/removed while listen was running (try restarting listen and moving/copying watched folder to a new location)
101
+
102
+ 4. Insufficient latency (for polling and rb-fsevent)
103
+ * too many files being watched (polling) and not enough threads or CPU power
104
+ * slow editor save (see below)
105
+ * slow hard drive
106
+ * encryption
107
+ * a combination of factors
108
+
109
+ 5. Too few or too many callbacks (`:wait_for_delay` option)
110
+ * complex editor file-save causes events to happen during callback (result: multiple callbacks if wait_for_delay is too small)
111
+ * too large when using TCP mode (see timestamps in output to debug)
112
+ * too many changes happening too frequently (use ignore rules to filter them out)
113
+
114
+ 6. Paths
115
+ * default ignore rules
116
+ * encoding-related issues (bad filenames, mounted FS encoding mismatch)
117
+ * symlinks may cause listen to hang (#259)
118
+ * symlinks may not work as you expect or work differently for polling vs non-polling
119
+ * TCP paths don't match with client's current working directory
120
+
121
+ 7. Editors
122
+ * "atomic save" in editors may confuse listen (disable it and try again)
123
+ * listen's default ignore rules may need tweaking
124
+ * your editor may not be supported yet (see default ignore rules for editors)
125
+ * use `touch foo` or `echo "a" >> foo` to confirm it's an editor issue
126
+ * slow terminal/GFX card, slow font, transparency effects in terminal
127
+ * complex/lengthy editor save (due to e.g. many plugins running during save)
128
+ * listen has complex rules for detecting atomic file saves (Linux)
129
+
130
+ 8. TCP (tcp mode) issues
131
+ * not a recent listen gem (before 2.7.11)
132
+ * additional network delay and collecting may need a higher `:wait_for_delay` value
133
+ * changes (added, removed, deleted) not matching actual changes
134
+
135
+ If your application keeps using the polling-adapter and you can't figure out why, feel free to [open an issue](https://github.com/guard/listen/issues/new) (and be sure to [give all the details](https://github.com/guard/listen/blob/master/CONTRIBUTING.md)).
136
+
137
+ Listen traps SIGINT signal to properly finalize listeners. If you plan
138
+ on trapping this signal yourself - make sure to call `Listen.stop` in
139
+ signal handler.
@@ -1,6 +1,8 @@
1
1
  require 'celluloid'
2
2
  require 'listen/listener'
3
3
 
4
+ require 'listen/internals/thread_pool'
5
+
4
6
  module Listen
5
7
  class << self
6
8
  # Listens to file system modifications on a either single directory or
@@ -21,11 +23,9 @@ module Listen
21
23
  def to(*args, &block)
22
24
  Celluloid.boot unless Celluloid.running?
23
25
  options = args.last.is_a?(Hash) ? args.last : {}
24
- if target = options.delete(:forward_to)
25
- _add_listener(target, :broadcaster, *args, &block)
26
- else
27
- _add_listener(*args, &block)
28
- end
26
+ target = options.delete(:forward_to)
27
+ args = ([target, :broadcaster] + args) if target
28
+ _add_listener(*args, &block)
29
29
  end
30
30
 
31
31
  # Stop all listeners & Celluloid
@@ -36,6 +36,7 @@ module Listen
36
36
  # This is used by the `listen` binary to handle Ctrl-C
37
37
  #
38
38
  def stop
39
+ Internals::ThreadPool.stop
39
40
  @listeners ||= []
40
41
 
41
42
  # TODO: should use a mutex for this
@@ -22,7 +22,8 @@ module Listen
22
22
  _warn_polling_fallback(options)
23
23
  Polling
24
24
  rescue
25
- _log :warn, "Adapter: failed: #{$!.inspect}:#{$@.join("\n")}"
25
+ _log :warn, format('Adapter: failed: %s:%s', $ERROR_POSITION.inspect,
26
+ $ERROR_POSITION * "\n")
26
27
  raise
27
28
  end
28
29
 
@@ -28,7 +28,7 @@ module Listen
28
28
  defaults = self.class.const_get('DEFAULTS')
29
29
  @options = Listen::Options.new(options, defaults)
30
30
  rescue
31
- _log :error, "adapter config failed: #{$!}:#{$@.join("\n")}"
31
+ _log_exception 'adapter config failed: %s:%s'
32
32
  raise
33
33
  end
34
34
 
@@ -53,11 +53,11 @@ module Listen
53
53
 
54
54
  def start
55
55
  configure
56
- Thread.new do
56
+ Listen::Internals::ThreadPool.add do
57
57
  begin
58
58
  _run
59
59
  rescue
60
- _log :error, "run() in thread failed: #{$!}:#{$@.join("\n")}"
60
+ _log_exception 'run() in thread failed: %s:%s'
61
61
  raise
62
62
  end
63
63
  end
@@ -83,6 +83,10 @@ module Listen
83
83
  self.class.send(:_log, *args)
84
84
  end
85
85
 
86
+ def _log_exception(msg)
87
+ _log :error, format(msg, $ERROR_INFO, $ERROR_POSITION * "\n")
88
+ end
89
+
86
90
  def self._log(*args)
87
91
  Celluloid::Logger.send(*args)
88
92
  end
@@ -1,3 +1,5 @@
1
+ require 'listen/internals/thread_pool'
2
+
1
3
  module Listen
2
4
  module Adapter
3
5
  # Adapter implementation for Mac OS X `FSEvents`.
@@ -10,6 +12,7 @@ module Listen
10
12
 
11
13
  private
12
14
 
15
+ # NOTE: each directory gets a DIFFERENT callback!
13
16
  def _configure(dir, &callback)
14
17
  require 'rb-fsevent'
15
18
  opts = { latency: options.latency }
@@ -20,8 +23,20 @@ module Listen
20
23
  end
21
24
  end
22
25
 
26
+ # NOTE: _run is called within a thread, so run every other
27
+ # worker in it's own thread
23
28
  def _run
24
- @workers.pop.run while !@workers.empty?
29
+ first = @workers.pop
30
+ until @workers.empty?
31
+ Listen::Internals::ThreadPool.add do
32
+ begin
33
+ @workers.pop.run
34
+ rescue
35
+ _log_exception 'run() in extra thread(s) failed: %s: %s'
36
+ end
37
+ end
38
+ end
39
+ first.run
25
40
  end
26
41
 
27
42
  def _process_event(dir, event)
@@ -26,16 +26,18 @@ module Listen
26
26
  @buffer = ''
27
27
  async.run
28
28
  rescue Celluloid::Task::TerminatedError
29
- _log :debug, "TCP adapter was terminated: #{$!.inspect}"
29
+ _log :debug, "TCP adapter was terminated: #{$ERROR_INFO.inspect}"
30
30
  rescue Errno::ECONNREFUSED
31
31
  sleep 1
32
32
  attempts -= 1
33
- _log :warn, "TCP.start: #{$!.inspect}"
33
+ _log :warn, "TCP.start: #{$ERROR_INFO.inspect}"
34
34
  retry if attempts > 0
35
- _log :error, "TCP.start: #{$!.inspect}:#{$@.join("\n")}"
35
+ _log :error, format('TCP.start: %s:%s', $ERROR_INFO.inspect,
36
+ $ERROR_POSITION * "\n")
36
37
  raise
37
38
  rescue
38
- _log :error, "TCP.start: #{$!.inspect}:#{$@.join("\n")}"
39
+ _log :error, format('TCP.start: %s:%s', $ERROR_INFO.inspect,
40
+ $ERROR_POSITION * "\n")
39
41
  raise
40
42
  end
41
43
 
@@ -65,7 +67,8 @@ module Listen
65
67
  handle_message(message)
66
68
  end
67
69
  rescue
68
- _log :error, "TCP.handle_data crashed: #{$!}:#{$@.join("\n")}"
70
+ _log :error, format('TCP.handle_data crashed: %s:%s', $ERROR_INFO,
71
+ $ERROR_POSITION * "\n")
69
72
  raise
70
73
  end
71
74
 
@@ -7,10 +7,7 @@ module Listen
7
7
 
8
8
  BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
9
9
  Please add the following to your Gemfile to avoid polling for changes:
10
- require 'rbconfig'
11
- if RbConfig::CONFIG['target_os'] =~ /mswin|mingw|cygwin/i
12
- gem 'wdm', '>= 0.1.0'
13
- end
10
+ gem 'wdm', '>= 0.1.0' if Gem.win_platform?
14
11
  EOS
15
12
 
16
13
  def self.usable?
@@ -18,7 +15,9 @@ module Listen
18
15
  require 'wdm'
19
16
  true
20
17
  rescue LoadError
21
- _log :debug, "wdm - load failed: #{$!}:#{$@.join("\n")}"
18
+ _log :debug, format('wdm - load failed: %s:%s', $ERROR_INFO,
19
+ $ERROR_POSITION * "\n")
20
+
22
21
  Kernel.warn BUNDLER_DECLARE_GEM
23
22
  false
24
23
  end
@@ -82,7 +81,8 @@ module Listen
82
81
  end
83
82
  rescue
84
83
  details = event.inspect
85
- _log :error, "wdm - callback (#{details}): #{$!}:#{$@.join("\n")}"
84
+ _log :error, format('wdm - callback (%): %s:%s', details, $ERROR_INFO,
85
+ $ERROR_POSITION * "\n")
86
86
  raise
87
87
  end
88
88
 
@@ -43,9 +43,10 @@ module Listen
43
43
  end
44
44
  end
45
45
  rescue Celluloid::Task::TerminatedError
46
- _log :debug, "Change#change was terminated: #{$!.inspect}"
46
+ _log :debug, "Change#change was terminated: #{$ERROR_INFO.inspect}"
47
47
  rescue RuntimeError
48
- _log :error, "Change#change crashed #{$!.inspect}:#{$@.join("\n")}"
48
+ _log :error, format('Change#change crashed %s:%s', $ERROR_INFO.inspect,
49
+ $ERROR_POSITION * "\n")
49
50
  raise
50
51
  end
51
52
 
@@ -55,9 +55,7 @@ module Listen
55
55
  listener = Listen.to directory, forward_to: address, &callback
56
56
  listener.start
57
57
 
58
- while listener.listen?
59
- sleep 0.5
60
- end
58
+ sleep 0.5 while listener.listen?
61
59
  end
62
60
  end
63
61
  end
@@ -13,16 +13,10 @@ module Listen
13
13
  path = dir + rel_path
14
14
  current = Set.new(path.children)
15
15
 
16
- if options[:silence]
17
- _log(:debug) do
18
- "Recording: #{rel_path}: #{options.inspect}"\
19
- " [#{previous.inspect}] -> (#{current.inspect})"
20
- end
21
- else
22
- _log(:debug) do
23
- "Scanning: #{rel_path}: #{options.inspect}"\
24
- " [#{previous.inspect}] -> (#{current.inspect})"
25
- end
16
+ _log(:debug) do
17
+ format('%s: %s(%s): %s -> %s',
18
+ (options[:silence] ? 'Recording' : 'Scanning'),
19
+ rel_path, options.inspect, previous.inspect, current.inspect)
26
20
  end
27
21
 
28
22
  current.each do |full_path|
@@ -46,7 +40,9 @@ module Listen
46
40
  _async_changes(dir, path, queue, previous, options)
47
41
  _change(queue, :file, dir, rel_path, options)
48
42
  rescue
49
- _log(:warn) { "scanning DIED: #{$!}:#{$@.join("\n")}" }
43
+ _log(:warn) do
44
+ format('scan DIED: %s:%s', $ERROR_INFO, $ERROR_POSITION * "\n")
45
+ end
50
46
  raise
51
47
  end
52
48
 
@@ -23,52 +23,46 @@ module Listen
23
23
  return :modified
24
24
  end
25
25
 
26
- unless /1|true/ =~ ENV['LISTEN_GEM_DISABLE_HASHING']
27
- if self.inaccurate_mac_time?(lstat)
28
- # Check if change happened within 1 second (maybe it's even
29
- # too much, e.g. 0.3-0.5 could be sufficient).
30
- #
31
- # With rb-fsevent, there's a (configurable) latency between
32
- # when file was changed and when the event was triggered.
33
- #
34
- # If a file is saved at ???14.998, by the time the event is
35
- # actually received by Listen, the time could already be e.g.
36
- # ???15.7.
37
- #
38
- # And since Darwin adapter uses directory scanning, the file
39
- # mtime may be the same (e.g. file was changed at ???14.001,
40
- # then at ???14.998, but the fstat time would be ???14.0 in
41
- # both cases).
42
- #
43
- # If change happend at ???14.999997, the mtime is 14.0, so for
44
- # an mtime=???14.0 we assume it could even be almost ???15.0
45
- #
46
- # So if Time.now.to_f is ???15.999998 and stat reports mtime
47
- # at ???14.0, then event was due to that file'd change when:
48
- #
49
- # ???15.999997 - ???14.999998 < 1.0s
50
- #
51
- # So the "2" is "1 + 1" (1s to cover rb-fsevent latency +
52
- # 1s maximum difference between real mtime and that recorded
53
- # in the file system)
54
- #
55
- if data[:mtime].to_i + 2 > Time.now.to_f
56
- begin
57
- md5 = Digest::MD5.file(path).digest
58
- record.async.update_file(dir, rel_path, data.merge(md5: md5))
59
- :modified if record_data[:md5] && md5 != record_data[:md5]
26
+ return if /1|true/ =~ ENV['LISTEN_GEM_DISABLE_HASHING']
27
+ return unless self.inaccurate_mac_time?(lstat)
60
28
 
61
- rescue SystemCallError
62
- # ignore failed md5
63
- end
64
- end
65
- end
66
- end
29
+ # Check if change happened within 1 second (maybe it's even
30
+ # too much, e.g. 0.3-0.5 could be sufficient).
31
+ #
32
+ # With rb-fsevent, there's a (configurable) latency between
33
+ # when file was changed and when the event was triggered.
34
+ #
35
+ # If a file is saved at ???14.998, by the time the event is
36
+ # actually received by Listen, the time could already be e.g.
37
+ # ???15.7.
38
+ #
39
+ # And since Darwin adapter uses directory scanning, the file
40
+ # mtime may be the same (e.g. file was changed at ???14.001,
41
+ # then at ???14.998, but the fstat time would be ???14.0 in
42
+ # both cases).
43
+ #
44
+ # If change happend at ???14.999997, the mtime is 14.0, so for
45
+ # an mtime=???14.0 we assume it could even be almost ???15.0
46
+ #
47
+ # So if Time.now.to_f is ???15.999998 and stat reports mtime
48
+ # at ???14.0, then event was due to that file'd change when:
49
+ #
50
+ # ???15.999997 - ???14.999998 < 1.0s
51
+ #
52
+ # So the "2" is "1 + 1" (1s to cover rb-fsevent latency +
53
+ # 1s maximum difference between real mtime and that recorded
54
+ # in the file system)
55
+ #
56
+ return if data[:mtime].to_i + 2 <= Time.now.to_f
57
+
58
+ md5 = Digest::MD5.file(path).digest
59
+ record.async.update_file(dir, rel_path, data.merge(md5: md5))
60
+ :modified if record_data[:md5] && md5 != record_data[:md5]
67
61
  rescue SystemCallError
68
62
  record.async.unset_path(dir, rel_path)
69
63
  :removed
70
64
  rescue
71
- Celluloid::Logger.debug "lstat failed for: #{rel_path} (#{$!})"
65
+ Celluloid::Logger.debug "lstat failed for: #{rel_path} (#{$ERROR_INFO})"
72
66
  raise
73
67
  end
74
68