concurrent-ruby 1.2.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile +1 -1
- data/README.md +2 -0
- data/Rakefile +7 -6
- data/lib/concurrent-ruby/concurrent/array.rb +3 -3
- data/lib/concurrent-ruby/concurrent/atomic/locals.rb +1 -0
- data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +2 -2
- data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +16 -8
- 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 +36 -33
- 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 +2 -4
- 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
@@ -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
|
@@ -11,6 +11,7 @@ module Concurrent
|
|
11
11
|
def initialize
|
12
12
|
@processor_count = Delay.new { compute_processor_count }
|
13
13
|
@physical_processor_count = Delay.new { compute_physical_processor_count }
|
14
|
+
@cpu_quota = Delay.new { compute_cpu_quota }
|
14
15
|
end
|
15
16
|
|
16
17
|
def processor_count
|
@@ -21,6 +22,25 @@ module Concurrent
|
|
21
22
|
@physical_processor_count.value
|
22
23
|
end
|
23
24
|
|
25
|
+
def available_processor_count
|
26
|
+
cpu_count = processor_count.to_f
|
27
|
+
quota = cpu_quota
|
28
|
+
|
29
|
+
return cpu_count if quota.nil?
|
30
|
+
|
31
|
+
# cgroup cpus quotas have no limits, so they can be set to higher than the
|
32
|
+
# real count of cores.
|
33
|
+
if quota > cpu_count
|
34
|
+
cpu_count
|
35
|
+
else
|
36
|
+
quota
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def cpu_quota
|
41
|
+
@cpu_quota.value
|
42
|
+
end
|
43
|
+
|
24
44
|
private
|
25
45
|
|
26
46
|
def compute_processor_count
|
@@ -60,6 +80,24 @@ module Concurrent
|
|
60
80
|
rescue
|
61
81
|
return 1
|
62
82
|
end
|
83
|
+
|
84
|
+
def compute_cpu_quota
|
85
|
+
if RbConfig::CONFIG["target_os"].include?("linux")
|
86
|
+
if File.exist?("/sys/fs/cgroup/cpu.max")
|
87
|
+
# cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
|
88
|
+
cpu_max = File.read("/sys/fs/cgroup/cpu.max")
|
89
|
+
return nil if cpu_max.start_with?("max ") # no limit
|
90
|
+
max, period = cpu_max.split.map(&:to_f)
|
91
|
+
max / period
|
92
|
+
elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
|
93
|
+
# cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
|
94
|
+
max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i
|
95
|
+
return nil if max == 0
|
96
|
+
period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f
|
97
|
+
max / period
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
63
101
|
end
|
64
102
|
end
|
65
103
|
|
@@ -107,4 +145,31 @@ module Concurrent
|
|
107
145
|
def self.physical_processor_count
|
108
146
|
processor_counter.physical_processor_count
|
109
147
|
end
|
148
|
+
|
149
|
+
# Number of processors cores available for process scheduling.
|
150
|
+
# Returns `nil` if there is no #cpu_quota, or a `Float` if the
|
151
|
+
# process is inside a cgroup with a dedicated CPU quota (typically Docker).
|
152
|
+
#
|
153
|
+
# For performance reasons the calculated value will be memoized on the first
|
154
|
+
# call.
|
155
|
+
#
|
156
|
+
# @return [nil, Float] number of available processors
|
157
|
+
def self.available_processor_count
|
158
|
+
processor_counter.available_processor_count
|
159
|
+
end
|
160
|
+
|
161
|
+
# The maximum number of processors cores available for process scheduling.
|
162
|
+
# Returns `nil` if there is no enforced limit, or a `Float` if the
|
163
|
+
# process is inside a cgroup with a dedicated CPU quota (typically Docker).
|
164
|
+
#
|
165
|
+
# Note that nothing prevents setting a CPU quota higher than the actual number of
|
166
|
+
# cores on the system.
|
167
|
+
#
|
168
|
+
# For performance reasons the calculated value will be memoized on the first
|
169
|
+
# call.
|
170
|
+
#
|
171
|
+
# @return [nil, Float] Maximum number of available processors as set by a cgroup CPU quota, or nil if none set
|
172
|
+
def self.cpu_quota
|
173
|
+
processor_counter.cpu_quota
|
174
|
+
end
|
110
175
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: concurrent-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jerry D'Antonio
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2024-05-29 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: |
|
16
16
|
Modern concurrency tools including agents, futures, promises, thread pools, actors, supervisors, and more.
|
@@ -76,7 +76,6 @@ files:
|
|
76
76
|
- lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb
|
77
77
|
- lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb
|
78
78
|
- lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb
|
79
|
-
- lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb
|
80
79
|
- lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb
|
81
80
|
- lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb
|
82
81
|
- lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb
|
@@ -147,7 +146,6 @@ files:
|
|
147
146
|
- lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb
|
148
147
|
- lib/concurrent-ruby/concurrent/thread_safe/util.rb
|
149
148
|
- lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb
|
150
|
-
- lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb
|
151
149
|
- lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb
|
152
150
|
- lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb
|
153
151
|
- lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb
|