Skip to content

Commit 010f140

Browse files
jonathanhefnerioquatix
authored andcommitted
Prevent race condition in Notifier#run / #stop
There is a potential race condition when a notifier is run in a new thread and then immediately stopped in the original thread. If `Notifier#stop` sets `@stop = true` before `Notifier#run` sets `@stop = false`, then `Notifier#run` will loop until `Notifier#stop` is called again. Furthermore, if `Notifier#run` acquires the `@running` mutex before `Notifier#stop` does (but after `Notifier#stop` sets `@stop = true`), then `Notifier#stop` will get stuck waiting for the mutex while `Notifier#run` infinitely loops. This commit modifies `Notifier#run` to assume `@stop == false` when it begins, and to reset `@stop = false` only after its loop terminates, thus eliminating the race.
1 parent 34e3db4 commit 010f140

File tree

2 files changed

+16
-1
lines changed

2 files changed

+16
-1
lines changed

lib/rb-inotify/notifier.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def fd
5050
# @raise [SystemCallError] if inotify failed to initialize for some reason
5151
def initialize
5252
@running = Mutex.new
53+
@stop = false
5354
@pipe = IO.pipe
5455
# JRuby shutdown sometimes runs IO finalizers before all threads finish.
5556
if RUBY_ENGINE == 'jruby'
@@ -231,12 +232,12 @@ def watch(path, *flags, &callback)
231232
def run
232233
@running.synchronize do
233234
Thread.current[:INOTIFY_RUN_THREAD] = true
234-
@stop = false
235235

236236
process until @stop
237237
end
238238
ensure
239239
Thread.current[:INOTIFY_RUN_THREAD] = false
240+
@stop = false
240241
end
241242

242243
# Stop watching for filesystem events.

spec/notifier_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@
107107
dir.join("one.txt").write("hello world")
108108
run_thread.join(1) or raise "timeout"
109109
end
110+
111+
it "can be stopped before it starts processing" do
112+
barrier = Concurrent::Event.new
113+
114+
run_thread = Thread.new do
115+
barrier.wait
116+
@notifier.run
117+
end
118+
119+
@notifier.stop
120+
barrier.set
121+
122+
run_thread.join(1) or raise "timeout"
123+
end
110124
end
111125

112126
describe :fd do

0 commit comments

Comments
 (0)