logstash-input-file 4.1.3 → 4.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/JAR_VERSION +1 -1
  4. data/README.md +0 -3
  5. data/docs/index.asciidoc +26 -16
  6. data/lib/filewatch/bootstrap.rb +10 -21
  7. data/lib/filewatch/discoverer.rb +35 -28
  8. data/lib/filewatch/observing_base.rb +2 -1
  9. data/lib/filewatch/read_mode/handlers/base.rb +19 -6
  10. data/lib/filewatch/read_mode/handlers/read_file.rb +43 -32
  11. data/lib/filewatch/read_mode/handlers/read_zip_file.rb +8 -3
  12. data/lib/filewatch/read_mode/processor.rb +8 -8
  13. data/lib/filewatch/settings.rb +3 -3
  14. data/lib/filewatch/sincedb_collection.rb +56 -42
  15. data/lib/filewatch/sincedb_value.rb +6 -0
  16. data/lib/filewatch/stat/generic.rb +34 -0
  17. data/lib/filewatch/stat/windows_path.rb +32 -0
  18. data/lib/filewatch/tail_mode/handlers/base.rb +40 -22
  19. data/lib/filewatch/tail_mode/handlers/create.rb +1 -2
  20. data/lib/filewatch/tail_mode/handlers/create_initial.rb +2 -1
  21. data/lib/filewatch/tail_mode/handlers/delete.rb +13 -1
  22. data/lib/filewatch/tail_mode/handlers/grow.rb +5 -2
  23. data/lib/filewatch/tail_mode/handlers/shrink.rb +7 -4
  24. data/lib/filewatch/tail_mode/handlers/unignore.rb +4 -2
  25. data/lib/filewatch/tail_mode/processor.rb +147 -58
  26. data/lib/filewatch/watch.rb +15 -35
  27. data/lib/filewatch/watched_file.rb +237 -41
  28. data/lib/filewatch/watched_files_collection.rb +2 -2
  29. data/lib/filewatch/winhelper.rb +167 -25
  30. data/lib/jars/filewatch-1.0.1.jar +0 -0
  31. data/lib/logstash/inputs/file.rb +9 -2
  32. data/logstash-input-file.gemspec +9 -2
  33. data/spec/file_ext/file_ext_windows_spec.rb +36 -0
  34. data/spec/filewatch/read_mode_handlers_read_file_spec.rb +2 -2
  35. data/spec/filewatch/reading_spec.rb +100 -57
  36. data/spec/filewatch/rotate_spec.rb +451 -0
  37. data/spec/filewatch/spec_helper.rb +33 -10
  38. data/spec/filewatch/tailing_spec.rb +273 -153
  39. data/spec/filewatch/watched_file_spec.rb +3 -3
  40. data/spec/filewatch/watched_files_collection_spec.rb +3 -3
  41. data/spec/filewatch/winhelper_spec.rb +4 -5
  42. data/spec/helpers/logging_level_helper.rb +8 -0
  43. data/spec/helpers/rspec_wait_handler_helper.rb +38 -0
  44. data/spec/helpers/spec_helper.rb +7 -1
  45. data/spec/inputs/file_read_spec.rb +54 -24
  46. data/spec/inputs/file_tail_spec.rb +244 -284
  47. metadata +13 -3
  48. data/lib/jars/filewatch-1.0.0.jar +0 -0
@@ -10,8 +10,7 @@ module FileWatch module TailMode module Handlers
10
10
 
11
11
  def update_existing_specifically(watched_file, sincedb_value)
12
12
  # sincedb_value is the source of truth
13
- position = sincedb_value.position
14
- watched_file.update_bytes_read(position)
13
+ watched_file.update_bytes_read(sincedb_value.position)
15
14
  end
16
15
  end
17
16
  end end end
@@ -4,6 +4,7 @@ module FileWatch module TailMode module Handlers
4
4
  class CreateInitial < Base
5
5
  def handle_specifically(watched_file)
6
6
  if open_file(watched_file)
7
+ logger.trace("handle_specifically opened file handle: #{watched_file.file.fileno}, path: #{watched_file.filename}")
7
8
  add_or_update_sincedb_collection(watched_file)
8
9
  end
9
10
  end
@@ -13,7 +14,7 @@ module FileWatch module TailMode module Handlers
13
14
  if @settings.start_new_files_at == :beginning
14
15
  position = 0
15
16
  end
16
- logger.debug("update_existing_specifically - #{watched_file.path}: seeking to #{position}")
17
+ logger.trace("update_existing_specifically - #{watched_file.path}: seeking to #{position}")
17
18
  watched_file.update_bytes_read(position)
18
19
  sincedb_value.update_position(position)
19
20
  end
@@ -2,9 +2,21 @@
2
2
 
3
3
  module FileWatch module TailMode module Handlers
4
4
  class Delete < Base
5
+ DATA_LOSS_WARNING = "watched file path was deleted or rotated before all content was read, if the file is found again it will be read from the last position"
5
6
  def handle_specifically(watched_file)
7
+ # TODO consider trying to find the renamed file - it will have the same inode.
8
+ # Needs a rotate scheme rename hint from user e.g. "<name>-YYYY-MM-DD-N.<ext>" or "<name>.<ext>.N"
9
+ # send the found content to the same listener (stream identity)
10
+ logger.trace("info",
11
+ "watched_file details" => watched_file.details,
12
+ "path" => watched_file.path)
13
+ if watched_file.bytes_unread > 0
14
+ logger.warn(DATA_LOSS_WARNING, "unread_bytes" => watched_file.bytes_unread, "path" => watched_file.path)
15
+ end
6
16
  watched_file.listener.deleted
7
- sincedb_collection.unset_watched_file(watched_file)
17
+ # no need to worry about data in the buffer
18
+ # if found it will be associated by inode and read from last position
19
+ sincedb_collection.watched_file_deleted(watched_file)
8
20
  watched_file.file_close
9
21
  end
10
22
  end
@@ -4,8 +4,11 @@ module FileWatch module TailMode module Handlers
4
4
  class Grow < Base
5
5
  def handle_specifically(watched_file)
6
6
  watched_file.file_seek(watched_file.bytes_read)
7
- logger.debug("reading to eof: #{watched_file.path}")
8
- read_to_eof(watched_file)
7
+ loop do
8
+ loop_control = watched_file.loop_control_adjusted_for_stat_size
9
+ controlled_read(watched_file, loop_control)
10
+ break unless loop_control.more
11
+ end
9
12
  end
10
13
  end
11
14
  end end end
@@ -5,16 +5,19 @@ module FileWatch module TailMode module Handlers
5
5
  def handle_specifically(watched_file)
6
6
  add_or_update_sincedb_collection(watched_file)
7
7
  watched_file.file_seek(watched_file.bytes_read)
8
- logger.debug("reading to eof: #{watched_file.path}")
9
- read_to_eof(watched_file)
8
+ loop do
9
+ loop_control = watched_file.loop_control_adjusted_for_stat_size
10
+ controlled_read(watched_file, loop_control)
11
+ break unless loop_control.more
12
+ end
10
13
  end
11
14
 
12
15
  def update_existing_specifically(watched_file, sincedb_value)
13
16
  # we have a match but size is smaller
14
17
  # set all to zero
15
- logger.debug("update_existing_specifically: #{watched_file.path}: was truncated seeking to beginning")
16
- watched_file.update_bytes_read(0) if watched_file.bytes_read != 0
18
+ watched_file.reset_bytes_unread
17
19
  sincedb_value.update_position(0)
20
+ logger.trace("update_existing_specifically: was truncated seeking to beginning", "watched file" => watched_file.details, "sincedb value" => sincedb_value)
18
21
  end
19
22
  end
20
23
  end end end
@@ -6,15 +6,16 @@ module FileWatch module TailMode module Handlers
6
6
  # before any other handling has been done
7
7
  # at a minimum we create or associate a sincedb value
8
8
  def handle_specifically(watched_file)
9
- add_or_update_sincedb_collection(watched_file) unless sincedb_collection.member?(watched_file.sincedb_key)
9
+ add_or_update_sincedb_collection(watched_file)
10
10
  end
11
11
 
12
12
  def get_new_value_specifically(watched_file)
13
13
  # for file initially ignored their bytes_read was set to stat.size
14
14
  # use this value not the `start_new_files_at` for the position
15
- # logger.debug("get_new_value_specifically", "watched_file" => watched_file.inspect)
15
+ # logger.trace("get_new_value_specifically", "watched_file" => watched_file.inspect)
16
16
  SincedbValue.new(watched_file.bytes_read).tap do |val|
17
17
  val.set_watched_file(watched_file)
18
+ logger.trace("-------------------- >>>>> get_new_value_specifically: unignore", "watched file" => watched_file.details, "sincedb value" => val)
18
19
  end
19
20
  end
20
21
 
@@ -25,6 +26,7 @@ module FileWatch module TailMode module Handlers
25
26
  # we will handle grow or shrink
26
27
  # for now we seek to where we were before the file got ignored (grow)
27
28
  # or to the start (shrink)
29
+ logger.trace("-------------------- >>>>> update_existing_specifically: unignore", "watched file" => watched_file.details, "sincedb value" => sincedb_value)
28
30
  position = 0
29
31
  if watched_file.shrunk?
30
32
  watched_file.update_bytes_read(0)
@@ -34,6 +34,7 @@ module FileWatch module TailMode
34
34
  end
35
35
 
36
36
  def initialize_handlers(sincedb_collection, observer)
37
+ @sincedb_collection = sincedb_collection
37
38
  @create_initial = Handlers::CreateInitial.new(sincedb_collection, observer, @settings)
38
39
  @create = Handlers::Create.new(sincedb_collection, observer, @settings)
39
40
  @grow = Handlers::Grow.new(sincedb_collection, observer, @settings)
@@ -71,80 +72,148 @@ module FileWatch module TailMode
71
72
  @unignore.handle(watched_file)
72
73
  end
73
74
 
75
+ def process_all_states(watched_files)
76
+ process_closed(watched_files)
77
+ return if watch.quit?
78
+ process_ignored(watched_files)
79
+ return if watch.quit?
80
+ process_delayed_delete(watched_files)
81
+ return if watch.quit?
82
+ process_restat_for_watched_and_active(watched_files)
83
+ return if watch.quit?
84
+ process_rotation_in_progress(watched_files)
85
+ return if watch.quit?
86
+ process_watched(watched_files)
87
+ return if watch.quit?
88
+ process_active(watched_files)
89
+ end
90
+
91
+ private
92
+
74
93
  def process_closed(watched_files)
75
- logger.debug("Closed processing")
94
+ # logger.trace("Closed processing")
76
95
  # Handles watched_files in the closed state.
77
96
  # if its size changed it is put into the watched state
78
97
  watched_files.select {|wf| wf.closed? }.each do |watched_file|
79
- path = watched_file.path
80
- begin
81
- watched_file.restat
98
+ common_restat_with_delay(watched_file, "Closed") do
99
+ # it won't do this if rotation is detected
82
100
  if watched_file.size_changed?
83
101
  # if the closed file changed, move it to the watched state
84
102
  # not to active state because we want to respect the active files window.
85
103
  watched_file.watch
86
104
  end
87
- rescue Errno::ENOENT
88
- # file has gone away or we can't read it anymore.
89
- common_deleted_reaction(watched_file, "Closed")
90
- rescue => e
91
- common_error_reaction(path, e, "Closed")
92
105
  end
93
106
  break if watch.quit?
94
107
  end
95
108
  end
96
109
 
97
110
  def process_ignored(watched_files)
98
- logger.debug("Ignored processing")
111
+ # logger.trace("Ignored processing")
99
112
  # Handles watched_files in the ignored state.
100
113
  # if its size changed:
101
114
  # put it in the watched state
102
115
  # invoke unignore
103
116
  watched_files.select {|wf| wf.ignored? }.each do |watched_file|
104
- path = watched_file.path
105
- begin
106
- watched_file.restat
117
+ common_restat_with_delay(watched_file, "Ignored") do
118
+ # it won't do this if rotation is detected
107
119
  if watched_file.size_changed?
108
120
  watched_file.watch
109
121
  unignore(watched_file)
110
122
  end
111
- rescue Errno::ENOENT
112
- # file has gone away or we can't read it anymore.
113
- common_deleted_reaction(watched_file, "Ignored")
114
- rescue => e
115
- common_error_reaction(path, e, "Ignored")
116
123
  end
117
124
  break if watch.quit?
118
125
  end
119
126
  end
120
127
 
128
+ def process_delayed_delete(watched_files)
129
+ # defer the delete to one loop later to ensure that the stat really really can't find a renamed file
130
+ # because a `stat` can be called right in the middle of the rotation rename cascade
131
+ logger.trace("Delayed Delete processing")
132
+ watched_files.select {|wf| wf.delayed_delete?}.each do |watched_file|
133
+ logger.trace(">>> Delayed Delete", "path" => watched_file.filename)
134
+ common_restat_without_delay(watched_file, ">>> Delayed Delete") do
135
+ logger.trace(">>> Delayed Delete: file at path found again", "watched_file" => watched_file.details)
136
+ watched_file.file_at_path_found_again
137
+ end
138
+ end
139
+ end
140
+
141
+ def process_restat_for_watched_and_active(watched_files)
142
+ # do restat on all watched and active states once now. closed and ignored have been handled already
143
+ logger.trace("Watched + Active restat processing")
144
+ watched_files.select {|wf| wf.watched? || wf.active?}.each do |watched_file|
145
+ common_restat_with_delay(watched_file, "Watched")
146
+ end
147
+ end
148
+
149
+ def process_rotation_in_progress(watched_files)
150
+ logger.trace("Rotation In Progress processing")
151
+ watched_files.select {|wf| wf.rotation_in_progress?}.each do |watched_file|
152
+ if !watched_file.all_read?
153
+ if watched_file.file_open?
154
+ # rotated file but original opened file is not fully read
155
+ # we need to keep reading the open file, if we close it we lose it because the path is now pointing at a different file.
156
+ logger.trace(">>> Rotation In Progress - inode change detected and original content is not fully read, reading all", "watched_file details" => watched_file.details)
157
+ # need to fully read open file while we can
158
+ watched_file.set_maximum_read_loop
159
+ grow(watched_file)
160
+ watched_file.set_standard_read_loop
161
+ else
162
+ logger.warn(">>> Rotation In Progress - inode change detected and original content is not fully read, file is closed and path points to new content", "watched_file details" => watched_file.details)
163
+ end
164
+ end
165
+ current_key = watched_file.sincedb_key
166
+ sdb_value = @sincedb_collection.get(current_key)
167
+ potential_key = watched_file.stat_sincedb_key
168
+ potential_sdb_value = @sincedb_collection.get(potential_key)
169
+ logger.trace(">>> Rotation In Progress", "watched_file" => watched_file.details, "found_sdb_value" => sdb_value, "potential_key" => potential_key, "potential_sdb_value" => potential_sdb_value)
170
+ if potential_sdb_value.nil?
171
+ if sdb_value.nil?
172
+ logger.trace("---------- >>> Rotation In Progress: rotating as initial file, no potential sincedb value AND no found sincedb value")
173
+ watched_file.rotate_as_initial_file
174
+ else
175
+ logger.trace("---------- >>>> Rotation In Progress: rotating as existing file, no potential sincedb value BUT found sincedb value")
176
+ watched_file.rotate_as_file
177
+ sdb_value.clear_watched_file
178
+ end
179
+ new_sdb_value = SincedbValue.new(0)
180
+ new_sdb_value.set_watched_file(watched_file)
181
+ @sincedb_collection.set(potential_key, new_sdb_value)
182
+ else
183
+ other_watched_file = potential_sdb_value.watched_file
184
+ if other_watched_file.nil?
185
+ logger.trace("---------- >>>> Rotation In Progress: rotating as existing file WITH potential sincedb value that does not have a watched file reference !!!!!!!!!!!!!!!!!")
186
+ watched_file.rotate_as_file(potential_sdb_value.position)
187
+ sdb_value.clear_watched_file unless sdb_value.nil?
188
+ potential_sdb_value.set_watched_file(watched_file)
189
+ else
190
+ logger.trace("---------- >>>> Rotation In Progress: rotating from...", "this watched_file details" => watched_file.details, "other watched_file details" => other_watched_file.details)
191
+ watched_file.rotate_from(other_watched_file)
192
+ sdb_value.clear_watched_file unless sdb_value.nil?
193
+ potential_sdb_value.set_watched_file(watched_file)
194
+ end
195
+ end
196
+ logger.trace("---------- >>>> Rotation In Progress: after handling rotation", "this watched_file details" => watched_file.details, "sincedb_value" => (potential_sdb_value || sdb_value))
197
+ end
198
+ end
199
+
121
200
  def process_watched(watched_files)
122
- logger.debug("Watched processing")
123
201
  # Handles watched_files in the watched state.
124
202
  # for a slice of them:
125
203
  # move to the active state
126
204
  # and we allow the block to open the file and create a sincedb collection record if needed
127
205
  # some have never been active and some have
128
206
  # those that were active before but are watched now were closed under constraint
129
-
207
+ logger.trace("Watched processing")
130
208
  # how much of the max active window is available
131
209
  to_take = @settings.max_active - watched_files.count{|wf| wf.active?}
132
210
  if to_take > 0
133
211
  watched_files.select {|wf| wf.watched?}.take(to_take).each do |watched_file|
134
- path = watched_file.path
135
- begin
136
- watched_file.restat
137
- watched_file.activate
138
- if watched_file.initial?
139
- create_initial(watched_file)
140
- else
141
- create(watched_file)
142
- end
143
- rescue Errno::ENOENT
144
- # file has gone away or we can't read it anymore.
145
- common_deleted_reaction(watched_file, "Watched")
146
- rescue => e
147
- common_error_reaction(path, e, "Watched")
212
+ watched_file.activate
213
+ if watched_file.initial?
214
+ create_initial(watched_file)
215
+ else
216
+ create(watched_file)
148
217
  end
149
218
  break if watch.quit?
150
219
  end
@@ -159,51 +228,71 @@ module FileWatch module TailMode
159
228
  end
160
229
 
161
230
  def process_active(watched_files)
162
- logger.debug("Active processing")
231
+ # logger.trace("Active processing")
163
232
  # Handles watched_files in the active state.
164
- # it has been read once - unless they were empty at the time
233
+ # files have been opened at this point
165
234
  watched_files.select {|wf| wf.active? }.each do |watched_file|
166
- path = watched_file.path
167
- begin
168
- watched_file.restat
169
- rescue Errno::ENOENT
170
- # file has gone away or we can't read it anymore.
171
- common_deleted_reaction(watched_file, "Active")
172
- next
173
- rescue => e
174
- common_error_reaction(path, e, "Active")
175
- next
176
- end
177
235
  break if watch.quit?
236
+ path = watched_file.filename
178
237
  if watched_file.grown?
179
- logger.debug("Active - file grew: #{path}: new size is #{watched_file.last_stat_size}, old size #{watched_file.bytes_read}")
238
+ logger.trace("Active - file grew: #{path}: new size is #{watched_file.last_stat_size}, bytes read #{watched_file.bytes_read}")
180
239
  grow(watched_file)
181
240
  elsif watched_file.shrunk?
241
+ if watched_file.bytes_unread > 0
242
+ logger.warn("Active - shrunk: DATA LOSS!! truncate detected with #{watched_file.bytes_unread} unread bytes: #{path}")
243
+ end
182
244
  # we don't update the size here, its updated when we actually read
183
- logger.debug("Active - file shrunk #{path}: new size is #{watched_file.last_stat_size}, old size #{watched_file.bytes_read}")
245
+ logger.trace("Active - file shrunk #{path}: new size is #{watched_file.last_stat_size}, old size #{watched_file.bytes_read}")
184
246
  shrink(watched_file)
185
247
  else
186
248
  # same size, do nothing
249
+ logger.trace("Active - no change", "watched_file" => watched_file.details)
187
250
  end
188
251
  # can any active files be closed to make way for waiting files?
189
252
  if watched_file.file_closable?
190
- logger.debug("Watch each: active: file expired: #{path}")
253
+ logger.trace("Watch each: active: file expired: #{path}")
191
254
  timeout(watched_file)
192
255
  watched_file.close
193
256
  end
194
257
  end
195
258
  end
196
259
 
197
- def common_deleted_reaction(watched_file, action)
198
- # file has gone away or we can't read it anymore.
199
- watched_file.unwatch
200
- delete(watched_file)
201
- deletable_filepaths << watched_file.path
202
- logger.debug("#{action} - stat failed: #{watched_file.path}, removing from collection")
260
+ def common_restat_with_delay(watched_file, action, &block)
261
+ common_restat(watched_file, action, true, &block)
203
262
  end
204
263
 
205
- def common_error_reaction(path, error, action)
206
- logger.error("#{action} - other error #{path}: (#{error.message}, #{error.backtrace.take(8).inspect})")
264
+ def common_restat_without_delay(watched_file, action, &block)
265
+ common_restat(watched_file, action, false, &block)
266
+ end
267
+
268
+ def common_restat(watched_file, action, delay, &block)
269
+ all_ok = true
270
+ begin
271
+ watched_file.restat
272
+ if watched_file.rotation_in_progress?
273
+ logger.trace("-------------------- >>>>> restat - rotation_detected", "watched_file details" => watched_file.details, "new sincedb key" => watched_file.stat_sincedb_key)
274
+ # don't yield to closed and ignore processing
275
+ else
276
+ yield if block_given?
277
+ end
278
+ rescue Errno::ENOENT
279
+ if delay
280
+ logger.trace("#{action} - delaying the stat fail on: #{watched_file.filename}")
281
+ watched_file.delay_delete
282
+ else
283
+ # file has gone away or we can't read it anymore.
284
+ logger.trace("#{action} - after a delay, really can't find this file: #{watched_file.filename}")
285
+ watched_file.unwatch
286
+ logger.trace("#{action} - removing from collection: #{watched_file.filename}")
287
+ delete(watched_file)
288
+ deletable_filepaths << watched_file.path
289
+ all_ok = false
290
+ end
291
+ rescue => e
292
+ logger.error("#{action} - other error #{watched_file.path}: (#{e.message}, #{e.backtrace.take(8).inspect})")
293
+ all_ok = false
294
+ end
295
+ all_ok
207
296
  end
208
297
  end
209
298
  end end
@@ -10,11 +10,8 @@ module FileWatch
10
10
 
11
11
  def initialize(discoverer, watched_files_collection, settings)
12
12
  @settings = settings
13
- # watch and iterate_on_state can be called from different threads.
14
- @lock = Mutex.new
15
13
  # we need to be threadsafe about the quit mutation
16
- @quit = false
17
- @quit_lock = Mutex.new
14
+ @quit = Concurrent::AtomicBoolean.new(false)
18
15
  @lastwarn_max_files = 0
19
16
  @discoverer = discoverer
20
17
  @watched_files_collection = watched_files_collection
@@ -27,17 +24,13 @@ module FileWatch
27
24
  end
28
25
 
29
26
  def watch(path)
30
- synchronized do
31
- @discoverer.add_path(path)
32
- end
27
+ @discoverer.add_path(path)
33
28
  # don't return whatever @discoverer.add_path returns
34
29
  return true
35
30
  end
36
31
 
37
32
  def discover
38
- synchronized do
39
- @discoverer.discover
40
- end
33
+ @discoverer.discover
41
34
  # don't return whatever @discoverer.discover returns
42
35
  return true
43
36
  end
@@ -60,6 +53,7 @@ module FileWatch
60
53
  break if quit?
61
54
  sleep(@settings.stat_interval)
62
55
  end
56
+ sincedb_collection.write_if_requested # does nothing if no requests to write were lodged.
63
57
  @watched_files_collection.close_all
64
58
  end # def subscribe
65
59
 
@@ -67,42 +61,28 @@ module FileWatch
67
61
  # differently from Tail mode - see the ReadMode::Processor and TailMode::Processor
68
62
  def iterate_on_state
69
63
  return if @watched_files_collection.empty?
70
- synchronized do
71
- begin
72
- # creates this snapshot of watched_file values just once
73
- watched_files = @watched_files_collection.values
74
- @processor.process_closed(watched_files)
75
- return if quit?
76
- @processor.process_ignored(watched_files)
77
- return if quit?
78
- @processor.process_watched(watched_files)
79
- return if quit?
80
- @processor.process_active(watched_files)
81
- ensure
82
- @watched_files_collection.delete(@processor.deletable_filepaths)
83
- @processor.deletable_filepaths.clear
84
- end
64
+ begin
65
+ # creates this snapshot of watched_file values just once
66
+ watched_files = @watched_files_collection.values
67
+ @processor.process_all_states(watched_files)
68
+ ensure
69
+ @watched_files_collection.delete(@processor.deletable_filepaths)
70
+ @processor.deletable_filepaths.clear
85
71
  end
86
72
  end # def each
87
73
 
88
74
  def quit
89
- @quit_lock.synchronize do
90
- @quit = true
91
- end
92
- end # def quit
75
+ @quit.make_true
76
+ end
93
77
 
94
78
  def quit?
95
- @quit_lock.synchronize { @quit }
79
+ @quit.true?
96
80
  end
97
81
 
98
82
  private
99
83
 
100
- def synchronized(&block)
101
- @lock.synchronize { block.call }
102
- end
103
-
104
84
  def reset_quit
105
- @quit_lock.synchronize { @quit = false }
85
+ @quit.make_false
106
86
  end
107
87
  end
108
88
  end