concurrent-ruby 1.2.2 → 1.3.6

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -2
  3. data/Gemfile +6 -6
  4. data/README.md +7 -3
  5. data/Rakefile +50 -25
  6. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +1 -1
  7. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +1 -1
  8. data/lib/concurrent-ruby/concurrent/agent.rb +2 -2
  9. data/lib/concurrent-ruby/concurrent/array.rb +3 -3
  10. data/lib/concurrent-ruby/concurrent/async.rb +1 -1
  11. data/lib/concurrent-ruby/concurrent/atom.rb +1 -1
  12. data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +1 -0
  13. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +23 -20
  14. data/lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb +55 -0
  15. data/lib/concurrent-ruby/concurrent/collection/timeout_queue.rb +18 -0
  16. data/lib/concurrent-ruby/concurrent/concern/logging.rb +17 -12
  17. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  18. data/lib/concurrent-ruby/concurrent/delay.rb +1 -1
  19. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +6 -4
  20. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +5 -7
  21. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +7 -0
  22. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +2 -0
  23. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +61 -31
  24. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +1 -1
  25. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +10 -3
  26. data/lib/concurrent-ruby/concurrent/executors.rb +0 -1
  27. data/lib/concurrent-ruby/concurrent/hash.rb +5 -3
  28. data/lib/concurrent-ruby/concurrent/map.rb +3 -3
  29. data/lib/concurrent-ruby/concurrent/mvar.rb +4 -4
  30. data/lib/concurrent-ruby/concurrent/promise.rb +2 -2
  31. data/lib/concurrent-ruby/concurrent/promises.rb +33 -23
  32. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +1 -1
  33. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +1 -1
  34. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +1 -1
  35. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +1 -1
  36. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +1 -1
  37. data/lib/concurrent-ruby/concurrent/timer_task.rb +65 -10
  38. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +116 -6
  39. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  40. metadata +5 -5
  41. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +0 -927
  42. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +0 -81
@@ -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 @yeild section).
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
- # Created resolvable event, user is responsible for resolving the event once by
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 rejection with
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 with will be fulfilled with the given value.
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 with will be rejected with the given reason.
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 of futures_and_or_events is fulfilled.
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 resolved future.
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 of the futures_and_or_events resolves.
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, @DefaultExecutor, executor, args, &task).future
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
- unless resolved?
776
- @Condition.wait @Lock, timeout
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, @DefaultExecutor, executor, args, &task).future
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, @DefaultExecutor, executor, args, &task).future
1065
+ RescuePromise.new_blocked_by1(self, executor, executor, args, &task).future
1056
1066
  end
1057
1067
 
1058
1068
  # @!macro promises.method.zip
@@ -193,7 +193,7 @@ module Concurrent
193
193
  end
194
194
  end
195
195
 
196
- # The `delay` value given at instanciation.
196
+ # The `delay` value given at instantiation.
197
197
  #
198
198
  # @return [Float] the initial delay.
199
199
  def initial_delay
@@ -157,7 +157,7 @@ module Concurrent
157
157
  end
158
158
  end
159
159
  members.each_with_index do |member, index|
160
- clazz.send :remove_method, member if clazz.instance_methods.include? member
160
+ clazz.send :remove_method, member if clazz.instance_methods(false).include? member
161
161
  clazz.send(:define_method, member) do
162
162
  @values[index]
163
163
  end
@@ -58,7 +58,7 @@ module Concurrent
58
58
 
59
59
  # Creates methods for reading and writing to a instance variable with
60
60
  # volatile (Java) semantic as {.attr_volatile} does.
61
- # The instance variable should be accessed oly through generated methods.
61
+ # The instance variable should be accessed only through generated methods.
62
62
  # This method generates following methods: `value`, `value=(new_value) #=> new_value`,
63
63
  # `swap_value(new_value) #=> old_value`,
64
64
  # `compare_and_set_value(expected, value) #=> true || false`, `update_value(&block)`.
@@ -9,7 +9,7 @@ module Concurrent
9
9
  # @!visibility private
10
10
  module Util
11
11
 
12
- # A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8
12
+ # A Ruby port of the Doug Lea's jsr166e.LongAdder class version 1.8
13
13
  # available in public domain.
14
14
  #
15
15
  # Original source code available here:
@@ -15,7 +15,7 @@ module Concurrent
15
15
  # Usage:
16
16
  # x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed
17
17
  # while true
18
- # if (x = XorShiftRandom.xorshift).odd? # thread-localy generate a next random number
18
+ # if (x = XorShiftRandom.xorshift).odd? # thread-locally generate a next random number
19
19
  # do_something_at_random
20
20
  # end
21
21
  # end
@@ -2,6 +2,7 @@ require 'concurrent/collection/copy_on_notify_observer_set'
2
2
  require 'concurrent/concern/dereferenceable'
3
3
  require 'concurrent/concern/observable'
4
4
  require 'concurrent/atomic/atomic_boolean'
5
+ require 'concurrent/atomic/atomic_fixnum'
5
6
  require 'concurrent/executor/executor_service'
6
7
  require 'concurrent/executor/ruby_executor_service'
7
8
  require 'concurrent/executor/safe_task_executor'
@@ -32,6 +33,17 @@ module Concurrent
32
33
  # be tested separately then passed to the `TimerTask` for scheduling and
33
34
  # running.
34
35
  #
36
+ # A `TimerTask` supports two different types of interval calculations.
37
+ # A fixed delay will always wait the same amount of time between the
38
+ # completion of one task and the start of the next. A fixed rate will
39
+ # attempt to maintain a constant rate of execution regardless of the
40
+ # duration of the task. For example, if a fixed rate task is scheduled
41
+ # to run every 60 seconds but the task itself takes 10 seconds to
42
+ # complete, the next task will be scheduled to run 50 seconds after
43
+ # the start of the previous task. If the task takes 70 seconds to
44
+ # complete, the next task will be start immediately after the previous
45
+ # task completes. Tasks will not be executed concurrently.
46
+ #
35
47
  # In some cases it may be necessary for a `TimerTask` to affect its own
36
48
  # execution cycle. To facilitate this, a reference to the TimerTask instance
37
49
  # is passed as an argument to the provided block every time the task is
@@ -74,6 +86,12 @@ module Concurrent
74
86
  #
75
87
  # #=> 'Boom!'
76
88
  #
89
+ # @example Configuring `:interval_type` with either :fixed_delay or :fixed_rate, default is :fixed_delay
90
+ # task = Concurrent::TimerTask.new(execution_interval: 5, interval_type: :fixed_rate) do
91
+ # puts 'Boom!'
92
+ # end
93
+ # task.interval_type #=> :fixed_rate
94
+ #
77
95
  # @example Last `#value` and `Dereferenceable` mixin
78
96
  # task = Concurrent::TimerTask.new(
79
97
  # dup_on_deref: true,
@@ -87,7 +105,7 @@ module Concurrent
87
105
  #
88
106
  # @example Controlling execution from within the block
89
107
  # timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task|
90
- # task.execution_interval.times{ print 'Boom! ' }
108
+ # task.execution_interval.to_i.times{ print 'Boom! ' }
91
109
  # print "\n"
92
110
  # task.execution_interval += 1
93
111
  # if task.execution_interval > 5
@@ -96,7 +114,7 @@ module Concurrent
96
114
  # end
97
115
  # end
98
116
  #
99
- # timer_task.execute # blocking call - this task will stop itself
117
+ # timer_task.execute
100
118
  # #=> Boom!
101
119
  # #=> Boom! Boom!
102
120
  # #=> Boom! Boom! Boom!
@@ -152,18 +170,30 @@ module Concurrent
152
170
  # Default `:execution_interval` in seconds.
153
171
  EXECUTION_INTERVAL = 60
154
172
 
155
- # Default `:timeout_interval` in seconds.
156
- TIMEOUT_INTERVAL = 30
173
+ # Maintain the interval between the end of one execution and the start of the next execution.
174
+ FIXED_DELAY = :fixed_delay
175
+
176
+ # Maintain the interval between the start of one execution and the start of the next.
177
+ # If execution time exceeds the interval, the next execution will start immediately
178
+ # after the previous execution finishes. Executions will not run concurrently.
179
+ FIXED_RATE = :fixed_rate
180
+
181
+ # Default `:interval_type`
182
+ DEFAULT_INTERVAL_TYPE = FIXED_DELAY
157
183
 
158
184
  # Create a new TimerTask with the given task and configuration.
159
185
  #
160
186
  # @!macro timer_task_initialize
161
187
  # @param [Hash] opts the options defining task execution.
162
- # @option opts [Integer] :execution_interval number of seconds between
188
+ # @option opts [Float] :execution_interval number of seconds between
163
189
  # task executions (default: EXECUTION_INTERVAL)
164
190
  # @option opts [Boolean] :run_now Whether to run the task immediately
165
191
  # upon instantiation or to wait until the first # execution_interval
166
192
  # has passed (default: false)
193
+ # @options opts [Symbol] :interval_type method to calculate the interval
194
+ # between executions, can be either :fixed_rate or :fixed_delay.
195
+ # (default: :fixed_delay)
196
+ # @option opts [Executor] executor, default is `global_io_executor`
167
197
  #
168
198
  # @!macro deref_options
169
199
  #
@@ -207,6 +237,7 @@ module Concurrent
207
237
  synchronize do
208
238
  if @running.false?
209
239
  @running.make_true
240
+ @age.increment
210
241
  schedule_next_task(@run_now ? 0 : @execution_interval)
211
242
  end
212
243
  end
@@ -242,6 +273,10 @@ module Concurrent
242
273
  end
243
274
  end
244
275
 
276
+ # @!attribute [r] interval_type
277
+ # @return [Symbol] method to calculate the interval between executions
278
+ attr_reader :interval_type
279
+
245
280
  # @!attribute [rw] timeout_interval
246
281
  # @return [Fixnum] Number of seconds the task can run before it is
247
282
  # considered to have failed.
@@ -264,12 +299,19 @@ module Concurrent
264
299
  set_deref_options(opts)
265
300
 
266
301
  self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
302
+ if opts[:interval_type] && ![FIXED_DELAY, FIXED_RATE].include?(opts[:interval_type])
303
+ raise ArgumentError.new('interval_type must be either :fixed_delay or :fixed_rate')
304
+ end
267
305
  if opts[:timeout] || opts[:timeout_interval]
268
306
  warn 'TimeTask timeouts are now ignored as these were not able to be implemented correctly'
269
307
  end
308
+
270
309
  @run_now = opts[:now] || opts[:run_now]
271
- @executor = Concurrent::SafeTaskExecutor.new(task)
310
+ @interval_type = opts[:interval_type] || DEFAULT_INTERVAL_TYPE
311
+ @task = Concurrent::SafeTaskExecutor.new(task)
312
+ @executor = opts[:executor] || Concurrent.global_io_executor
272
313
  @running = Concurrent::AtomicBoolean.new(false)
314
+ @age = Concurrent::AtomicFixnum.new(0)
273
315
  @value = nil
274
316
 
275
317
  self.observers = Collection::CopyOnNotifyObserverSet.new
@@ -289,17 +331,20 @@ module Concurrent
289
331
 
290
332
  # @!visibility private
291
333
  def schedule_next_task(interval = execution_interval)
292
- ScheduledTask.execute(interval, args: [Concurrent::Event.new], &method(:execute_task))
334
+ ScheduledTask.execute(interval, executor: @executor, args: [Concurrent::Event.new, @age.value], &method(:execute_task))
293
335
  nil
294
336
  end
295
337
 
296
338
  # @!visibility private
297
- def execute_task(completion)
339
+ def execute_task(completion, age_when_scheduled)
298
340
  return nil unless @running.true?
299
- _success, value, reason = @executor.execute(self)
341
+ return nil unless @age.value == age_when_scheduled
342
+
343
+ start_time = Concurrent.monotonic_time
344
+ _success, value, reason = @task.execute(self)
300
345
  if completion.try?
301
346
  self.value = value
302
- schedule_next_task
347
+ schedule_next_task(calculate_next_interval(start_time))
303
348
  time = Time.now
304
349
  observers.notify_observers do
305
350
  [time, self.value, reason]
@@ -307,5 +352,15 @@ module Concurrent
307
352
  end
308
353
  nil
309
354
  end
355
+
356
+ # @!visibility private
357
+ def calculate_next_interval(start_time)
358
+ if @interval_type == FIXED_RATE
359
+ run_time = Concurrent.monotonic_time - start_time
360
+ [execution_interval - run_time, 0].max
361
+ else # FIXED_DELAY
362
+ execution_interval
363
+ end
364
+ end
310
365
  end
311
366
  end
@@ -11,6 +11,8 @@ 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 }
15
+ @cpu_shares = Delay.new { compute_cpu_shares }
14
16
  end
15
17
 
16
18
  def processor_count
@@ -21,6 +23,29 @@ module Concurrent
21
23
  @physical_processor_count.value
22
24
  end
23
25
 
26
+ def available_processor_count
27
+ cpu_count = processor_count.to_f
28
+ quota = cpu_quota
29
+
30
+ return cpu_count if quota.nil?
31
+
32
+ # cgroup cpus quotas have no limits, so they can be set to higher than the
33
+ # real count of cores.
34
+ if quota > cpu_count
35
+ cpu_count
36
+ else
37
+ quota
38
+ end
39
+ end
40
+
41
+ def cpu_quota
42
+ @cpu_quota.value
43
+ end
44
+
45
+ def cpu_shares
46
+ @cpu_shares.value
47
+ end
48
+
24
49
  private
25
50
 
26
51
  def compute_processor_count
@@ -48,10 +73,20 @@ module Concurrent
48
73
  end
49
74
  cores.count
50
75
  when /mswin|mingw/
51
- require 'win32ole'
52
- result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
53
- "select NumberOfCores from Win32_Processor")
54
- result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
76
+ # Get-CimInstance introduced in PowerShell 3 or earlier: https://learn.microsoft.com/en-us/previous-versions/powershell/module/cimcmdlets/get-ciminstance?view=powershell-3.0
77
+ result = run('powershell -command "Get-CimInstance -ClassName Win32_Processor -Property NumberOfCores | Select-Object -Property NumberOfCores"')
78
+ if !result || $?.exitstatus != 0
79
+ # fallback to deprecated wmic for older systems
80
+ result = run("wmic cpu get NumberOfCores")
81
+ end
82
+ if !result || $?.exitstatus != 0
83
+ # Bail out if both commands returned something unexpected
84
+ processor_count
85
+ else
86
+ # powershell: "\nNumberOfCores\n-------------\n 4\n\n\n"
87
+ # wmic: "NumberOfCores \n\n4 \n\n\n\n"
88
+ result.scan(/\d+/).map(&:to_i).reduce(:+)
89
+ end
55
90
  else
56
91
  processor_count
57
92
  end
@@ -60,6 +95,45 @@ module Concurrent
60
95
  rescue
61
96
  return 1
62
97
  end
98
+
99
+ def run(command)
100
+ IO.popen(command, &:read)
101
+ rescue Errno::ENOENT
102
+ end
103
+
104
+ def compute_cpu_quota
105
+ if RbConfig::CONFIG["target_os"].include?("linux")
106
+ if File.exist?("/sys/fs/cgroup/cpu.max")
107
+ # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
108
+ cpu_max = File.read("/sys/fs/cgroup/cpu.max")
109
+ return nil if cpu_max.start_with?("max ") # no limit
110
+ max, period = cpu_max.split.map(&:to_f)
111
+ max / period
112
+ elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
113
+ # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
114
+ max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i
115
+ # If the cpu.cfs_quota_us is -1, cgroup does not adhere to any CPU time restrictions
116
+ # https://docs.kernel.org/scheduler/sched-bwc.html#management
117
+ return nil if max <= 0
118
+ period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f
119
+ max / period
120
+ end
121
+ end
122
+ end
123
+
124
+ def compute_cpu_shares
125
+ if RbConfig::CONFIG["target_os"].include?("linux")
126
+ if File.exist?("/sys/fs/cgroup/cpu.weight")
127
+ # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
128
+ # Ref: https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2254-cgroup-v2#phase-1-convert-from-cgroups-v1-settings-to-v2
129
+ weight = File.read("/sys/fs/cgroup/cpu.weight").to_f
130
+ ((((weight - 1) * 262142) / 9999) + 2) / 1024
131
+ elsif File.exist?("/sys/fs/cgroup/cpu/cpu.shares")
132
+ # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
133
+ File.read("/sys/fs/cgroup/cpu/cpu.shares").to_f / 1024
134
+ end
135
+ end
136
+ end
63
137
  end
64
138
  end
65
139
 
@@ -75,8 +149,8 @@ module Concurrent
75
149
  # `java.lang.Runtime.getRuntime.availableProcessors` will be used. According
76
150
  # to the Java documentation this "value may change during a particular
77
151
  # invocation of the virtual machine... [applications] should therefore
78
- # occasionally poll this property." Subsequently the result will NOT be
79
- # memoized under JRuby.
152
+ # occasionally poll this property." We still memoize this value once under
153
+ # JRuby.
80
154
  #
81
155
  # Otherwise Ruby's Etc.nprocessors will be used.
82
156
  #
@@ -107,4 +181,40 @@ module Concurrent
107
181
  def self.physical_processor_count
108
182
  processor_counter.physical_processor_count
109
183
  end
184
+
185
+ # Number of processors cores available for process scheduling.
186
+ # This method takes in account the CPU quota if the process is inside a cgroup with a
187
+ # dedicated CPU quota (typically Docker).
188
+ # Otherwise it returns the same value as #processor_count but as a Float.
189
+ #
190
+ # For performance reasons the calculated value will be memoized on the first
191
+ # call.
192
+ #
193
+ # @return [Float] number of available processors
194
+ def self.available_processor_count
195
+ processor_counter.available_processor_count
196
+ end
197
+
198
+ # The maximum number of processors cores available for process scheduling.
199
+ # Returns `nil` if there is no enforced limit, or a `Float` if the
200
+ # process is inside a cgroup with a dedicated CPU quota (typically Docker).
201
+ #
202
+ # Note that nothing prevents setting a CPU quota higher than the actual number of
203
+ # cores on the system.
204
+ #
205
+ # For performance reasons the calculated value will be memoized on the first
206
+ # call.
207
+ #
208
+ # @return [nil, Float] Maximum number of available processors as set by a cgroup CPU quota, or nil if none set
209
+ def self.cpu_quota
210
+ processor_counter.cpu_quota
211
+ end
212
+
213
+ # The CPU shares requested by the process. For performance reasons the calculated
214
+ # value will be memoized on the first call.
215
+ #
216
+ # @return [Float, nil] CPU shares requested by the process, or nil if not set
217
+ def self.cpu_shares
218
+ processor_counter.cpu_shares
219
+ end
110
220
  end
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '1.2.2'
2
+ VERSION = '1.3.6'
3
3
  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.2.2
4
+ version: 1.3.6
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: 2023-02-24 00:00:00.000000000 Z
13
+ date: 2025-12-13 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,13 +76,14 @@ 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
83
82
  - lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb
84
83
  - lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb
85
84
  - lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb
85
+ - lib/concurrent-ruby/concurrent/collection/ruby_timeout_queue.rb
86
+ - lib/concurrent-ruby/concurrent/collection/timeout_queue.rb
86
87
  - lib/concurrent-ruby/concurrent/concern/deprecation.rb
87
88
  - lib/concurrent-ruby/concurrent/concern/dereferenceable.rb
88
89
  - lib/concurrent-ruby/concurrent/concern/logging.rb
@@ -147,7 +148,6 @@ files:
147
148
  - lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb
148
149
  - lib/concurrent-ruby/concurrent/thread_safe/util.rb
149
150
  - lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb
150
- - lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb
151
151
  - lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb
152
152
  - lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb
153
153
  - lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb
@@ -183,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  - !ruby/object:Gem::Version
184
184
  version: '0'
185
185
  requirements: []
186
- rubygems_version: 3.3.26
186
+ rubygems_version: 3.3.27
187
187
  signing_key:
188
188
  specification_version: 4
189
189
  summary: Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell,