concurrent-ruby 1.2.2 → 1.3.1.pre
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/Gemfile +1 -1
- data/README.md +2 -0
- data/lib/concurrent-ruby/concurrent/array.rb +3 -3
- data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +23 -20
- data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +4 -0
- data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +4 -7
- data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +5 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +7 -0
- data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +6 -2
- data/lib/concurrent-ruby/concurrent/hash.rb +5 -3
- data/lib/concurrent-ruby/concurrent/map.rb +2 -2
- data/lib/concurrent-ruby/concurrent/promises.rb +33 -23
- data/lib/concurrent-ruby/concurrent/timer_task.rb +59 -9
- data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +65 -0
- data/lib/concurrent-ruby/concurrent/version.rb +1 -1
- metadata +4 -6
- data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +0 -927
- data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +0 -81
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b3f7a68382f7fab40500191814c5f26ae1bbe1cbc05d462c813584c28bbb548
|
4
|
+
data.tar.gz: f3ff0e09369fe9871bfe6de7e378e583ae750253f691a467fe9bda159cceebb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 118e62d97725f1394dc99738fc63ddb8a2bec0fb054f38b2d89acc3bd667e22ca010c9057313b465edfa612718816da1140743f8015e08c0b0d16b001b272961
|
7
|
+
data.tar.gz: c460ea75b66f76b34e42dddce248bceeab9d7c0166b4a80f6ab79d7c6978b815170f06558ea10c35ee94361122bc9974176afaef5c0f0532c887ac379b3c2301
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
## Current
|
2
2
|
|
3
|
+
## Release v1.3.1 (28 May 2024)
|
4
|
+
|
5
|
+
* Release 1.3.0 was broken when pushed to RubyGems. 1.3.1 is a packaging fix.
|
6
|
+
|
7
|
+
## Release v1.3.0 (28 May 2024)
|
8
|
+
|
9
|
+
* (#1042) Align Java Executor Service behavior for `shuttingdown?`, `shutdown?`
|
10
|
+
* (#1038) Add `Concurrent.usable_processor_count` that is cgroups aware.
|
11
|
+
|
12
|
+
## Release v1.2.3 (16 Jan 2024)
|
13
|
+
|
14
|
+
* See [the GitHub release](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.2.3) for details.
|
15
|
+
|
3
16
|
## Release v1.2.2 (24 Feb 2023)
|
4
17
|
|
5
18
|
* (#993) Fix arguments passed to `Concurrent::Map`'s `default_proc`.
|
data/Gemfile
CHANGED
@@ -12,7 +12,7 @@ gem 'concurrent-ruby-ext', Concurrent::VERSION, options.merge(platform: :mri)
|
|
12
12
|
|
13
13
|
group :development do
|
14
14
|
gem 'rake', '~> 13.0'
|
15
|
-
gem 'rake-compiler', '~> 1.0', '>= 1.0.7'
|
15
|
+
gem 'rake-compiler', '~> 1.0', '>= 1.0.7', '!= 1.2.4'
|
16
16
|
gem 'rake-compiler-dock', '~> 1.0'
|
17
17
|
gem 'pry', '~> 0.11', platforms: :mri
|
18
18
|
end
|
data/README.md
CHANGED
@@ -375,6 +375,8 @@ best practice is to depend on `concurrent-ruby` and let users to decide if they
|
|
375
375
|
* [Benoit Daloze](https://github.com/eregon)
|
376
376
|
* [Matthew Draper](https://github.com/matthewd)
|
377
377
|
* [Rafael França](https://github.com/rafaelfranca)
|
378
|
+
* [Charles Oliver Nutter](https://github.com/headius)
|
379
|
+
* [Ben Sheldon](https://github.com/bensheldon)
|
378
380
|
* [Samuel Williams](https://github.com/ioquatix)
|
379
381
|
|
380
382
|
### Special Thanks to
|
@@ -21,9 +21,9 @@ module Concurrent
|
|
21
21
|
# @!macro internal_implementation_note
|
22
22
|
ArrayImplementation = case
|
23
23
|
when Concurrent.on_cruby?
|
24
|
-
# Array is thread-safe
|
25
|
-
#
|
26
|
-
#
|
24
|
+
# Array is not fully thread-safe on CRuby, see
|
25
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/issues/929
|
26
|
+
# So we will need to add synchronization here
|
27
27
|
::Array
|
28
28
|
|
29
29
|
when Concurrent.on_jruby?
|
@@ -8,74 +8,77 @@ module Concurrent
|
|
8
8
|
# @!visibility private
|
9
9
|
class SynchronizedMapBackend < NonConcurrentMapBackend
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def initialize(*args, &block)
|
12
|
+
super
|
13
|
+
|
14
|
+
# WARNING: Mutex is a non-reentrant lock, so the synchronized methods are
|
15
|
+
# not allowed to call each other.
|
16
|
+
@mutex = Mutex.new
|
17
|
+
end
|
15
18
|
|
16
19
|
def [](key)
|
17
|
-
synchronize { super }
|
20
|
+
@mutex.synchronize { super }
|
18
21
|
end
|
19
22
|
|
20
23
|
def []=(key, value)
|
21
|
-
synchronize { super }
|
24
|
+
@mutex.synchronize { super }
|
22
25
|
end
|
23
26
|
|
24
27
|
def compute_if_absent(key)
|
25
|
-
synchronize { super }
|
28
|
+
@mutex.synchronize { super }
|
26
29
|
end
|
27
30
|
|
28
31
|
def compute_if_present(key)
|
29
|
-
synchronize { super }
|
32
|
+
@mutex.synchronize { super }
|
30
33
|
end
|
31
34
|
|
32
35
|
def compute(key)
|
33
|
-
synchronize { super }
|
36
|
+
@mutex.synchronize { super }
|
34
37
|
end
|
35
38
|
|
36
39
|
def merge_pair(key, value)
|
37
|
-
synchronize { super }
|
40
|
+
@mutex.synchronize { super }
|
38
41
|
end
|
39
42
|
|
40
43
|
def replace_pair(key, old_value, new_value)
|
41
|
-
synchronize { super }
|
44
|
+
@mutex.synchronize { super }
|
42
45
|
end
|
43
46
|
|
44
47
|
def replace_if_exists(key, new_value)
|
45
|
-
synchronize { super }
|
48
|
+
@mutex.synchronize { super }
|
46
49
|
end
|
47
50
|
|
48
51
|
def get_and_set(key, value)
|
49
|
-
synchronize { super }
|
52
|
+
@mutex.synchronize { super }
|
50
53
|
end
|
51
54
|
|
52
55
|
def key?(key)
|
53
|
-
synchronize { super }
|
56
|
+
@mutex.synchronize { super }
|
54
57
|
end
|
55
58
|
|
56
59
|
def delete(key)
|
57
|
-
synchronize { super }
|
60
|
+
@mutex.synchronize { super }
|
58
61
|
end
|
59
62
|
|
60
63
|
def delete_pair(key, value)
|
61
|
-
synchronize { super }
|
64
|
+
@mutex.synchronize { super }
|
62
65
|
end
|
63
66
|
|
64
67
|
def clear
|
65
|
-
synchronize { super }
|
68
|
+
@mutex.synchronize { super }
|
66
69
|
end
|
67
70
|
|
68
71
|
def size
|
69
|
-
synchronize { super }
|
72
|
+
@mutex.synchronize { super }
|
70
73
|
end
|
71
74
|
|
72
75
|
def get_or_default(key, default_value)
|
73
|
-
synchronize { super }
|
76
|
+
@mutex.synchronize { super }
|
74
77
|
end
|
75
78
|
|
76
79
|
private
|
77
80
|
def dupped_backend
|
78
|
-
synchronize { super }
|
81
|
+
@mutex.synchronize { super }
|
79
82
|
end
|
80
83
|
end
|
81
84
|
end
|
Binary file
|
@@ -39,6 +39,10 @@ module Concurrent
|
|
39
39
|
# The number of tasks that have been completed by the pool since construction.
|
40
40
|
# @return [Integer] The number of tasks that have been completed by the pool since construction.
|
41
41
|
|
42
|
+
# @!macro thread_pool_executor_method_active_count
|
43
|
+
# The number of threads that are actively executing tasks.
|
44
|
+
# @return [Integer] The number of threads that are actively executing tasks.
|
45
|
+
|
42
46
|
# @!macro thread_pool_executor_attr_reader_idletime
|
43
47
|
# The number of seconds that a thread may be idle before being reclaimed.
|
44
48
|
# @return [Integer] The number of seconds that a thread may be idle before being reclaimed.
|
@@ -57,15 +57,11 @@ if Concurrent.on_jruby?
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def ns_shuttingdown?
|
60
|
-
|
61
|
-
@executor.isTerminating
|
62
|
-
else
|
63
|
-
false
|
64
|
-
end
|
60
|
+
@executor.isShutdown && !@executor.isTerminated
|
65
61
|
end
|
66
62
|
|
67
63
|
def ns_shutdown?
|
68
|
-
@executor.
|
64
|
+
@executor.isTerminated
|
69
65
|
end
|
70
66
|
|
71
67
|
class Job
|
@@ -88,10 +84,11 @@ if Concurrent.on_jruby?
|
|
88
84
|
|
89
85
|
def initialize(daemonize = true)
|
90
86
|
@daemonize = daemonize
|
87
|
+
@java_thread_factory = java.util.concurrent.Executors.defaultThreadFactory
|
91
88
|
end
|
92
89
|
|
93
90
|
def newThread(runnable)
|
94
|
-
thread =
|
91
|
+
thread = @java_thread_factory.newThread(runnable)
|
95
92
|
thread.setDaemon(@daemonize)
|
96
93
|
return thread
|
97
94
|
end
|
@@ -73,6 +73,11 @@ if Concurrent.on_jruby?
|
|
73
73
|
@executor.getCompletedTaskCount
|
74
74
|
end
|
75
75
|
|
76
|
+
# @!macro thread_pool_executor_method_active_count
|
77
|
+
def active_count
|
78
|
+
@executor.getActiveCount
|
79
|
+
end
|
80
|
+
|
76
81
|
# @!macro thread_pool_executor_attr_reader_idletime
|
77
82
|
def idletime
|
78
83
|
@executor.getKeepAliveTime(java.util.concurrent.TimeUnit::SECONDS)
|
@@ -61,6 +61,13 @@ module Concurrent
|
|
61
61
|
synchronize { @completed_task_count }
|
62
62
|
end
|
63
63
|
|
64
|
+
# @!macro thread_pool_executor_method_active_count
|
65
|
+
def active_count
|
66
|
+
synchronize do
|
67
|
+
@pool.length - @ready.length
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
64
71
|
# @!macro executor_service_method_can_overflow_question
|
65
72
|
def can_overflow?
|
66
73
|
synchronize { ns_limited_queue? }
|
@@ -3,7 +3,7 @@ require 'concurrent/atomic/event'
|
|
3
3
|
require 'concurrent/collection/non_concurrent_priority_queue'
|
4
4
|
require 'concurrent/executor/executor_service'
|
5
5
|
require 'concurrent/executor/single_thread_executor'
|
6
|
-
|
6
|
+
require 'concurrent/errors'
|
7
7
|
require 'concurrent/options'
|
8
8
|
|
9
9
|
module Concurrent
|
@@ -162,7 +162,11 @@ module Concurrent
|
|
162
162
|
# queue now must have the same pop time, or a closer one, as
|
163
163
|
# when we peeked).
|
164
164
|
task = synchronize { @queue.pop }
|
165
|
-
|
165
|
+
begin
|
166
|
+
task.executor.post { task.process_task }
|
167
|
+
rescue RejectedExecutionError
|
168
|
+
# ignore and continue
|
169
|
+
end
|
166
170
|
else
|
167
171
|
@condition.wait([diff, 60].min)
|
168
172
|
end
|
@@ -15,9 +15,11 @@ module Concurrent
|
|
15
15
|
# @!macro internal_implementation_note
|
16
16
|
HashImplementation = case
|
17
17
|
when Concurrent.on_cruby?
|
18
|
-
# Hash is thread-safe
|
19
|
-
#
|
20
|
-
#
|
18
|
+
# Hash is not fully thread-safe on CRuby, see
|
19
|
+
# https://bugs.ruby-lang.org/issues/19237
|
20
|
+
# https://github.com/ruby/ruby/commit/ffd52412ab
|
21
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/issues/929
|
22
|
+
# So we will need to add synchronization here (similar to Concurrent::Map).
|
21
23
|
::Hash
|
22
24
|
|
23
25
|
when Concurrent.on_jruby?
|
@@ -20,8 +20,8 @@ module Concurrent
|
|
20
20
|
require 'concurrent/collection/map/truffleruby_map_backend'
|
21
21
|
TruffleRubyMapBackend
|
22
22
|
else
|
23
|
-
require 'concurrent/collection/map/
|
24
|
-
|
23
|
+
require 'concurrent/collection/map/synchronized_map_backend'
|
24
|
+
SynchronizedMapBackend
|
25
25
|
end
|
26
26
|
else
|
27
27
|
warn 'Concurrent::Map: unsupported Ruby engine, using a fully synchronized Concurrent::Map implementation'
|
@@ -5,6 +5,7 @@ require 'concurrent/collection/lock_free_stack'
|
|
5
5
|
require 'concurrent/configuration'
|
6
6
|
require 'concurrent/errors'
|
7
7
|
require 'concurrent/re_include'
|
8
|
+
require 'concurrent/utility/monotonic_time'
|
8
9
|
|
9
10
|
module Concurrent
|
10
11
|
|
@@ -22,7 +23,7 @@ module Concurrent
|
|
22
23
|
#
|
23
24
|
# @!macro promises.param.args
|
24
25
|
# @param [Object] args arguments which are passed to the task when it's executed.
|
25
|
-
# (It might be prepended with other arguments, see the @
|
26
|
+
# (It might be prepended with other arguments, see the @yield section).
|
26
27
|
#
|
27
28
|
# @!macro promises.shortcut.on
|
28
29
|
# Shortcut of {#$0_on} with default `:io` executor supplied.
|
@@ -63,8 +64,8 @@ module Concurrent
|
|
63
64
|
resolvable_event_on default_executor
|
64
65
|
end
|
65
66
|
|
66
|
-
#
|
67
|
-
# {Promises::ResolvableEvent#resolve}.
|
67
|
+
# Creates a resolvable event, user is responsible for resolving the event once
|
68
|
+
# by calling {Promises::ResolvableEvent#resolve}.
|
68
69
|
#
|
69
70
|
# @!macro promises.param.default_executor
|
70
71
|
# @return [ResolvableEvent]
|
@@ -94,7 +95,7 @@ module Concurrent
|
|
94
95
|
future_on(default_executor, *args, &task)
|
95
96
|
end
|
96
97
|
|
97
|
-
# Constructs new Future which will be resolved after block is evaluated on default executor.
|
98
|
+
# Constructs a new Future which will be resolved after block is evaluated on default executor.
|
98
99
|
# Evaluation begins immediately.
|
99
100
|
#
|
100
101
|
# @!macro promises.param.default_executor
|
@@ -106,7 +107,7 @@ module Concurrent
|
|
106
107
|
ImmediateEventPromise.new(default_executor).future.then(*args, &task)
|
107
108
|
end
|
108
109
|
|
109
|
-
# Creates resolved future with will be either fulfilled with the given value or
|
110
|
+
# Creates a resolved future with will be either fulfilled with the given value or rejected with
|
110
111
|
# the given reason.
|
111
112
|
#
|
112
113
|
# @param [true, false] fulfilled
|
@@ -118,7 +119,7 @@ module Concurrent
|
|
118
119
|
ImmediateFuturePromise.new(default_executor, fulfilled, value, reason).future
|
119
120
|
end
|
120
121
|
|
121
|
-
# Creates resolved future
|
122
|
+
# Creates a resolved future which will be fulfilled with the given value.
|
122
123
|
#
|
123
124
|
# @!macro promises.param.default_executor
|
124
125
|
# @param [Object] value
|
@@ -127,7 +128,7 @@ module Concurrent
|
|
127
128
|
resolved_future true, value, nil, default_executor
|
128
129
|
end
|
129
130
|
|
130
|
-
# Creates resolved future
|
131
|
+
# Creates a resolved future which will be rejected with the given reason.
|
131
132
|
#
|
132
133
|
# @!macro promises.param.default_executor
|
133
134
|
# @param [Object] reason
|
@@ -190,7 +191,7 @@ module Concurrent
|
|
190
191
|
delay_on default_executor, *args, &task
|
191
192
|
end
|
192
193
|
|
193
|
-
# Creates new event or future which is resolved only after it is touched,
|
194
|
+
# Creates a new event or future which is resolved only after it is touched,
|
194
195
|
# see {Concurrent::AbstractEventFuture#touch}.
|
195
196
|
#
|
196
197
|
# @!macro promises.param.default_executor
|
@@ -214,7 +215,7 @@ module Concurrent
|
|
214
215
|
schedule_on default_executor, intended_time, *args, &task
|
215
216
|
end
|
216
217
|
|
217
|
-
# Creates new event or future which is resolved in intended_time.
|
218
|
+
# Creates a new event or future which is resolved in intended_time.
|
218
219
|
#
|
219
220
|
# @!macro promises.param.default_executor
|
220
221
|
# @!macro promises.param.intended_time
|
@@ -240,8 +241,8 @@ module Concurrent
|
|
240
241
|
zip_futures_on default_executor, *futures_and_or_events
|
241
242
|
end
|
242
243
|
|
243
|
-
# Creates new future which is resolved after all futures_and_or_events are resolved.
|
244
|
-
# Its value is array of zipped future values. Its reason is array of reasons for rejection.
|
244
|
+
# Creates a new future which is resolved after all futures_and_or_events are resolved.
|
245
|
+
# Its value is an array of zipped future values. Its reason is an array of reasons for rejection.
|
245
246
|
# If there is an error it rejects.
|
246
247
|
# @!macro promises.event-conversion
|
247
248
|
# If event is supplied, which does not have value and can be only resolved, it's
|
@@ -262,7 +263,7 @@ module Concurrent
|
|
262
263
|
zip_events_on default_executor, *futures_and_or_events
|
263
264
|
end
|
264
265
|
|
265
|
-
# Creates new event which is resolved after all futures_and_or_events are resolved.
|
266
|
+
# Creates a new event which is resolved after all futures_and_or_events are resolved.
|
266
267
|
# (Future is resolved when fulfilled or rejected.)
|
267
268
|
#
|
268
269
|
# @!macro promises.param.default_executor
|
@@ -280,8 +281,8 @@ module Concurrent
|
|
280
281
|
|
281
282
|
alias_method :any, :any_resolved_future
|
282
283
|
|
283
|
-
# Creates new future which is resolved after first futures_and_or_events is resolved.
|
284
|
-
# Its result equals result of the first resolved future.
|
284
|
+
# Creates a new future which is resolved after the first futures_and_or_events is resolved.
|
285
|
+
# Its result equals the result of the first resolved future.
|
285
286
|
# @!macro promises.any-touch
|
286
287
|
# If resolved it does not propagate {Concurrent::AbstractEventFuture#touch}, leaving delayed
|
287
288
|
# futures un-executed if they are not required any more.
|
@@ -300,9 +301,9 @@ module Concurrent
|
|
300
301
|
any_fulfilled_future_on default_executor, *futures_and_or_events
|
301
302
|
end
|
302
303
|
|
303
|
-
# Creates new future which is resolved after first
|
304
|
-
# Its result equals result of the first resolved future or if all futures_and_or_events reject,
|
305
|
-
# it has reason of the last
|
304
|
+
# Creates a new future which is resolved after the first futures_and_or_events is fulfilled.
|
305
|
+
# Its result equals the result of the first resolved future or if all futures_and_or_events reject,
|
306
|
+
# it has reason of the last rejected future.
|
306
307
|
# @!macro promises.any-touch
|
307
308
|
# @!macro promises.event-conversion
|
308
309
|
#
|
@@ -319,7 +320,7 @@ module Concurrent
|
|
319
320
|
any_event_on default_executor, *futures_and_or_events
|
320
321
|
end
|
321
322
|
|
322
|
-
# Creates new event which becomes resolved after first
|
323
|
+
# Creates a new event which becomes resolved after the first futures_and_or_events resolves.
|
323
324
|
# @!macro promises.any-touch
|
324
325
|
#
|
325
326
|
# @!macro promises.param.default_executor
|
@@ -611,7 +612,7 @@ module Concurrent
|
|
611
612
|
# @yieldparam [Object] value
|
612
613
|
# @yieldparam [Object] reason
|
613
614
|
def chain_on(executor, *args, &task)
|
614
|
-
ChainPromise.new_blocked_by1(self,
|
615
|
+
ChainPromise.new_blocked_by1(self, executor, executor, args, &task).future
|
615
616
|
end
|
616
617
|
|
617
618
|
# @return [String] Short string representation.
|
@@ -772,8 +773,17 @@ module Concurrent
|
|
772
773
|
@Lock.synchronize do
|
773
774
|
@Waiters.increment
|
774
775
|
begin
|
775
|
-
|
776
|
-
|
776
|
+
if timeout
|
777
|
+
start = Concurrent.monotonic_time
|
778
|
+
until resolved?
|
779
|
+
break if @Condition.wait(@Lock, timeout) == nil # nil means timeout
|
780
|
+
timeout -= (Concurrent.monotonic_time - start)
|
781
|
+
break if timeout <= 0
|
782
|
+
end
|
783
|
+
else
|
784
|
+
until resolved?
|
785
|
+
@Condition.wait(@Lock, timeout)
|
786
|
+
end
|
777
787
|
end
|
778
788
|
ensure
|
779
789
|
# JRuby may raise ConcurrencyError
|
@@ -1034,7 +1044,7 @@ module Concurrent
|
|
1034
1044
|
# @return [Future]
|
1035
1045
|
# @yield [value, *args] to the task.
|
1036
1046
|
def then_on(executor, *args, &task)
|
1037
|
-
ThenPromise.new_blocked_by1(self,
|
1047
|
+
ThenPromise.new_blocked_by1(self, executor, executor, args, &task).future
|
1038
1048
|
end
|
1039
1049
|
|
1040
1050
|
# @!macro promises.shortcut.on
|
@@ -1052,7 +1062,7 @@ module Concurrent
|
|
1052
1062
|
# @return [Future]
|
1053
1063
|
# @yield [reason, *args] to the task.
|
1054
1064
|
def rescue_on(executor, *args, &task)
|
1055
|
-
RescuePromise.new_blocked_by1(self,
|
1065
|
+
RescuePromise.new_blocked_by1(self, executor, executor, args, &task).future
|
1056
1066
|
end
|
1057
1067
|
|
1058
1068
|
# @!macro promises.method.zip
|
@@ -32,6 +32,17 @@ module Concurrent
|
|
32
32
|
# be tested separately then passed to the `TimerTask` for scheduling and
|
33
33
|
# running.
|
34
34
|
#
|
35
|
+
# A `TimerTask` supports two different types of interval calculations.
|
36
|
+
# A fixed delay will always wait the same amount of time between the
|
37
|
+
# completion of one task and the start of the next. A fixed rate will
|
38
|
+
# attempt to maintain a constant rate of execution regardless of the
|
39
|
+
# duration of the task. For example, if a fixed rate task is scheduled
|
40
|
+
# to run every 60 seconds but the task itself takes 10 seconds to
|
41
|
+
# complete, the next task will be scheduled to run 50 seconds after
|
42
|
+
# the start of the previous task. If the task takes 70 seconds to
|
43
|
+
# complete, the next task will be start immediately after the previous
|
44
|
+
# task completes. Tasks will not be executed concurrently.
|
45
|
+
#
|
35
46
|
# In some cases it may be necessary for a `TimerTask` to affect its own
|
36
47
|
# execution cycle. To facilitate this, a reference to the TimerTask instance
|
37
48
|
# is passed as an argument to the provided block every time the task is
|
@@ -74,6 +85,12 @@ module Concurrent
|
|
74
85
|
#
|
75
86
|
# #=> 'Boom!'
|
76
87
|
#
|
88
|
+
# @example Configuring `:interval_type` with either :fixed_delay or :fixed_rate, default is :fixed_delay
|
89
|
+
# task = Concurrent::TimerTask.new(execution_interval: 5, interval_type: :fixed_rate) do
|
90
|
+
# puts 'Boom!'
|
91
|
+
# end
|
92
|
+
# task.interval_type #=> :fixed_rate
|
93
|
+
#
|
77
94
|
# @example Last `#value` and `Dereferenceable` mixin
|
78
95
|
# task = Concurrent::TimerTask.new(
|
79
96
|
# dup_on_deref: true,
|
@@ -87,7 +104,7 @@ module Concurrent
|
|
87
104
|
#
|
88
105
|
# @example Controlling execution from within the block
|
89
106
|
# timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task|
|
90
|
-
# task.execution_interval.times{ print 'Boom! ' }
|
107
|
+
# task.execution_interval.to_i.times{ print 'Boom! ' }
|
91
108
|
# print "\n"
|
92
109
|
# task.execution_interval += 1
|
93
110
|
# if task.execution_interval > 5
|
@@ -96,7 +113,7 @@ module Concurrent
|
|
96
113
|
# end
|
97
114
|
# end
|
98
115
|
#
|
99
|
-
# timer_task.execute
|
116
|
+
# timer_task.execute
|
100
117
|
# #=> Boom!
|
101
118
|
# #=> Boom! Boom!
|
102
119
|
# #=> Boom! Boom! Boom!
|
@@ -152,18 +169,30 @@ module Concurrent
|
|
152
169
|
# Default `:execution_interval` in seconds.
|
153
170
|
EXECUTION_INTERVAL = 60
|
154
171
|
|
155
|
-
#
|
156
|
-
|
172
|
+
# Maintain the interval between the end of one execution and the start of the next execution.
|
173
|
+
FIXED_DELAY = :fixed_delay
|
174
|
+
|
175
|
+
# Maintain the interval between the start of one execution and the start of the next.
|
176
|
+
# If execution time exceeds the interval, the next execution will start immediately
|
177
|
+
# after the previous execution finishes. Executions will not run concurrently.
|
178
|
+
FIXED_RATE = :fixed_rate
|
179
|
+
|
180
|
+
# Default `:interval_type`
|
181
|
+
DEFAULT_INTERVAL_TYPE = FIXED_DELAY
|
157
182
|
|
158
183
|
# Create a new TimerTask with the given task and configuration.
|
159
184
|
#
|
160
185
|
# @!macro timer_task_initialize
|
161
186
|
# @param [Hash] opts the options defining task execution.
|
162
|
-
# @option opts [
|
187
|
+
# @option opts [Float] :execution_interval number of seconds between
|
163
188
|
# task executions (default: EXECUTION_INTERVAL)
|
164
189
|
# @option opts [Boolean] :run_now Whether to run the task immediately
|
165
190
|
# upon instantiation or to wait until the first # execution_interval
|
166
191
|
# has passed (default: false)
|
192
|
+
# @options opts [Symbol] :interval_type method to calculate the interval
|
193
|
+
# between executions, can be either :fixed_rate or :fixed_delay.
|
194
|
+
# (default: :fixed_delay)
|
195
|
+
# @option opts [Executor] executor, default is `global_io_executor`
|
167
196
|
#
|
168
197
|
# @!macro deref_options
|
169
198
|
#
|
@@ -242,6 +271,10 @@ module Concurrent
|
|
242
271
|
end
|
243
272
|
end
|
244
273
|
|
274
|
+
# @!attribute [r] interval_type
|
275
|
+
# @return [Symbol] method to calculate the interval between executions
|
276
|
+
attr_reader :interval_type
|
277
|
+
|
245
278
|
# @!attribute [rw] timeout_interval
|
246
279
|
# @return [Fixnum] Number of seconds the task can run before it is
|
247
280
|
# considered to have failed.
|
@@ -264,11 +297,17 @@ module Concurrent
|
|
264
297
|
set_deref_options(opts)
|
265
298
|
|
266
299
|
self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
|
300
|
+
if opts[:interval_type] && ![FIXED_DELAY, FIXED_RATE].include?(opts[:interval_type])
|
301
|
+
raise ArgumentError.new('interval_type must be either :fixed_delay or :fixed_rate')
|
302
|
+
end
|
267
303
|
if opts[:timeout] || opts[:timeout_interval]
|
268
304
|
warn 'TimeTask timeouts are now ignored as these were not able to be implemented correctly'
|
269
305
|
end
|
306
|
+
|
270
307
|
@run_now = opts[:now] || opts[:run_now]
|
271
|
-
@
|
308
|
+
@interval_type = opts[:interval_type] || DEFAULT_INTERVAL_TYPE
|
309
|
+
@task = Concurrent::SafeTaskExecutor.new(task)
|
310
|
+
@executor = opts[:executor] || Concurrent.global_io_executor
|
272
311
|
@running = Concurrent::AtomicBoolean.new(false)
|
273
312
|
@value = nil
|
274
313
|
|
@@ -289,17 +328,18 @@ module Concurrent
|
|
289
328
|
|
290
329
|
# @!visibility private
|
291
330
|
def schedule_next_task(interval = execution_interval)
|
292
|
-
ScheduledTask.execute(interval, args: [Concurrent::Event.new], &method(:execute_task))
|
331
|
+
ScheduledTask.execute(interval, executor: @executor, args: [Concurrent::Event.new], &method(:execute_task))
|
293
332
|
nil
|
294
333
|
end
|
295
334
|
|
296
335
|
# @!visibility private
|
297
336
|
def execute_task(completion)
|
298
337
|
return nil unless @running.true?
|
299
|
-
|
338
|
+
start_time = Concurrent.monotonic_time
|
339
|
+
_success, value, reason = @task.execute(self)
|
300
340
|
if completion.try?
|
301
341
|
self.value = value
|
302
|
-
schedule_next_task
|
342
|
+
schedule_next_task(calculate_next_interval(start_time))
|
303
343
|
time = Time.now
|
304
344
|
observers.notify_observers do
|
305
345
|
[time, self.value, reason]
|
@@ -307,5 +347,15 @@ module Concurrent
|
|
307
347
|
end
|
308
348
|
nil
|
309
349
|
end
|
350
|
+
|
351
|
+
# @!visibility private
|
352
|
+
def calculate_next_interval(start_time)
|
353
|
+
if @interval_type == FIXED_RATE
|
354
|
+
run_time = Concurrent.monotonic_time - start_time
|
355
|
+
[execution_interval - run_time, 0].max
|
356
|
+
else # FIXED_DELAY
|
357
|
+
execution_interval
|
358
|
+
end
|
359
|
+
end
|
310
360
|
end
|
311
361
|
end
|