concurrent-ruby 1.2.2 → 1.3.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|