Skip to content

Commit 263feda

Browse files
committed
Allow TimerTask to be safely restarted after shutdown and avoid duplicate tasks
1 parent eae2851 commit 263feda

File tree

2 files changed

+20
-2
lines changed

2 files changed

+20
-2
lines changed

lib/concurrent-ruby/concurrent/timer_task.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ def execute
236236
synchronize do
237237
if @running.false?
238238
@running.make_true
239+
@age.increment
239240
schedule_next_task(@run_now ? 0 : @execution_interval)
240241
end
241242
end
@@ -309,6 +310,7 @@ def ns_initialize(opts, &task)
309310
@task = Concurrent::SafeTaskExecutor.new(task)
310311
@executor = opts[:executor] || Concurrent.global_io_executor
311312
@running = Concurrent::AtomicBoolean.new(false)
313+
@age = Concurrent::AtomicFixnum.new(0)
312314
@value = nil
313315

314316
self.observers = Collection::CopyOnNotifyObserverSet.new
@@ -328,13 +330,15 @@ def ns_kill_execution
328330

329331
# @!visibility private
330332
def schedule_next_task(interval = execution_interval)
331-
ScheduledTask.execute(interval, executor: @executor, args: [Concurrent::Event.new], &method(:execute_task))
333+
ScheduledTask.execute(interval, executor: @executor, args: [Concurrent::Event.new, @age.value], &method(:execute_task))
332334
nil
333335
end
334336

335337
# @!visibility private
336-
def execute_task(completion)
338+
def execute_task(completion, age_when_scheduled)
337339
return nil unless @running.true?
340+
return nil unless @age.value == age_when_scheduled
341+
338342
start_time = Concurrent.monotonic_time
339343
_success, value, reason = @task.execute(self)
340344
if completion.try?

spec/concurrent/timer_task_spec.rb

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require_relative 'concern/dereferenceable_shared'
22
require_relative 'concern/observable_shared'
3+
require 'concurrent/atomic/atomic_fixnum'
34
require 'concurrent/timer_task'
45

56
module Concurrent
@@ -116,6 +117,19 @@ def trigger_observable(observable)
116117
sleep(0.1)
117118
expect(task.shutdown).to be_truthy
118119
end
120+
121+
it 'will cancel pre-shutdown task even if restarted to avoid double-runs' do
122+
counter = Concurrent::AtomicFixnum.new(0)
123+
task = TimerTask.execute(execution_interval: 0.2, run_now: true) { counter.increment }
124+
sleep 0.05
125+
expect(counter.value).to eq 1
126+
127+
task.shutdown
128+
task.execute
129+
130+
sleep 0.25
131+
expect(counter.value).to eq 3
132+
end
119133
end
120134
end
121135

0 commit comments

Comments
 (0)