concurrent-ruby 0.8.0 → 0.9.0.pre2

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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -2
  3. data/README.md +103 -54
  4. data/lib/concurrent.rb +34 -14
  5. data/lib/concurrent/async.rb +164 -50
  6. data/lib/concurrent/atom.rb +171 -0
  7. data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
  8. data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
  9. data/lib/concurrent/atomic/atomic_reference.rb +49 -0
  10. data/lib/concurrent/atomic/condition.rb +23 -12
  11. data/lib/concurrent/atomic/count_down_latch.rb +23 -21
  12. data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
  13. data/lib/concurrent/atomic/event.rb +33 -42
  14. data/lib/concurrent/atomic/read_write_lock.rb +252 -0
  15. data/lib/concurrent/atomic/semaphore.rb +64 -89
  16. data/lib/concurrent/atomic/thread_local_var.rb +130 -58
  17. data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
  18. data/lib/concurrent/atomic_reference/direct_update.rb +3 -0
  19. data/lib/concurrent/atomic_reference/jruby.rb +6 -3
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +10 -32
  21. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
  22. data/lib/concurrent/atomic_reference/rbx.rb +4 -1
  23. data/lib/concurrent/atomic_reference/ruby.rb +6 -3
  24. data/lib/concurrent/atomics.rb +74 -4
  25. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
  26. data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
  27. data/lib/concurrent/collection/priority_queue.rb +300 -245
  28. data/lib/concurrent/concern/deprecation.rb +27 -0
  29. data/lib/concurrent/concern/dereferenceable.rb +88 -0
  30. data/lib/concurrent/concern/logging.rb +25 -0
  31. data/lib/concurrent/concern/obligation.rb +228 -0
  32. data/lib/concurrent/concern/observable.rb +85 -0
  33. data/lib/concurrent/configuration.rb +226 -112
  34. data/lib/concurrent/dataflow.rb +2 -3
  35. data/lib/concurrent/delay.rb +141 -50
  36. data/lib/concurrent/edge.rb +30 -0
  37. data/lib/concurrent/errors.rb +10 -0
  38. data/lib/concurrent/exchanger.rb +25 -1
  39. data/lib/concurrent/executor/cached_thread_pool.rb +46 -33
  40. data/lib/concurrent/executor/executor.rb +46 -299
  41. data/lib/concurrent/executor/executor_service.rb +521 -0
  42. data/lib/concurrent/executor/fixed_thread_pool.rb +206 -26
  43. data/lib/concurrent/executor/immediate_executor.rb +9 -9
  44. data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
  45. data/lib/concurrent/executor/java_cached_thread_pool.rb +18 -16
  46. data/lib/concurrent/executor/java_fixed_thread_pool.rb +11 -18
  47. data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
  48. data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
  49. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +9 -18
  50. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +10 -21
  51. data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
  52. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
  53. data/lib/concurrent/executor/safe_task_executor.rb +5 -4
  54. data/lib/concurrent/executor/serialized_execution.rb +22 -18
  55. data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
  56. data/lib/concurrent/executor/single_thread_executor.rb +32 -21
  57. data/lib/concurrent/executor/thread_pool_executor.rb +72 -60
  58. data/lib/concurrent/executor/timer_set.rb +96 -84
  59. data/lib/concurrent/executors.rb +1 -1
  60. data/lib/concurrent/future.rb +70 -38
  61. data/lib/concurrent/immutable_struct.rb +89 -0
  62. data/lib/concurrent/ivar.rb +152 -60
  63. data/lib/concurrent/lazy_register.rb +40 -20
  64. data/lib/concurrent/maybe.rb +226 -0
  65. data/lib/concurrent/mutable_struct.rb +227 -0
  66. data/lib/concurrent/mvar.rb +44 -43
  67. data/lib/concurrent/promise.rb +208 -134
  68. data/lib/concurrent/scheduled_task.rb +339 -43
  69. data/lib/concurrent/settable_struct.rb +127 -0
  70. data/lib/concurrent/synchronization.rb +17 -0
  71. data/lib/concurrent/synchronization/abstract_object.rb +163 -0
  72. data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
  73. data/lib/concurrent/synchronization/condition.rb +53 -0
  74. data/lib/concurrent/synchronization/java_object.rb +35 -0
  75. data/lib/concurrent/synchronization/lock.rb +32 -0
  76. data/lib/concurrent/synchronization/monitor_object.rb +24 -0
  77. data/lib/concurrent/synchronization/mutex_object.rb +43 -0
  78. data/lib/concurrent/synchronization/object.rb +78 -0
  79. data/lib/concurrent/synchronization/rbx_object.rb +75 -0
  80. data/lib/concurrent/timer_task.rb +87 -100
  81. data/lib/concurrent/tvar.rb +42 -38
  82. data/lib/concurrent/utilities.rb +3 -1
  83. data/lib/concurrent/utility/at_exit.rb +97 -0
  84. data/lib/concurrent/utility/engine.rb +40 -0
  85. data/lib/concurrent/utility/monotonic_time.rb +59 -0
  86. data/lib/concurrent/utility/native_extension_loader.rb +56 -0
  87. data/lib/concurrent/utility/processor_counter.rb +156 -0
  88. data/lib/concurrent/utility/timeout.rb +18 -14
  89. data/lib/concurrent/utility/timer.rb +11 -6
  90. data/lib/concurrent/version.rb +2 -1
  91. data/lib/concurrent_ruby.rb +1 -0
  92. metadata +47 -83
  93. data/lib/concurrent/actor.rb +0 -103
  94. data/lib/concurrent/actor/behaviour.rb +0 -70
  95. data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
  96. data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
  97. data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
  98. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
  99. data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
  100. data/lib/concurrent/actor/behaviour/linking.rb +0 -45
  101. data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
  102. data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
  103. data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
  104. data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
  105. data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
  106. data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
  107. data/lib/concurrent/actor/behaviour/termination.rb +0 -54
  108. data/lib/concurrent/actor/context.rb +0 -154
  109. data/lib/concurrent/actor/core.rb +0 -217
  110. data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
  111. data/lib/concurrent/actor/envelope.rb +0 -41
  112. data/lib/concurrent/actor/errors.rb +0 -27
  113. data/lib/concurrent/actor/internal_delegations.rb +0 -49
  114. data/lib/concurrent/actor/public_delegations.rb +0 -40
  115. data/lib/concurrent/actor/reference.rb +0 -81
  116. data/lib/concurrent/actor/root.rb +0 -37
  117. data/lib/concurrent/actor/type_check.rb +0 -48
  118. data/lib/concurrent/actor/utils.rb +0 -10
  119. data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
  120. data/lib/concurrent/actor/utils/balancer.rb +0 -42
  121. data/lib/concurrent/actor/utils/broadcast.rb +0 -52
  122. data/lib/concurrent/actor/utils/pool.rb +0 -59
  123. data/lib/concurrent/actress.rb +0 -3
  124. data/lib/concurrent/agent.rb +0 -209
  125. data/lib/concurrent/atomic.rb +0 -92
  126. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
  127. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
  128. data/lib/concurrent/atomic/synchronization.rb +0 -51
  129. data/lib/concurrent/channel/buffered_channel.rb +0 -85
  130. data/lib/concurrent/channel/channel.rb +0 -41
  131. data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
  132. data/lib/concurrent/channel/waitable_list.rb +0 -40
  133. data/lib/concurrent/channels.rb +0 -5
  134. data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
  135. data/lib/concurrent/collection/ring_buffer.rb +0 -59
  136. data/lib/concurrent/collections.rb +0 -3
  137. data/lib/concurrent/dereferenceable.rb +0 -108
  138. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
  139. data/lib/concurrent/logging.rb +0 -20
  140. data/lib/concurrent/obligation.rb +0 -171
  141. data/lib/concurrent/observable.rb +0 -73
  142. data/lib/concurrent/options_parser.rb +0 -52
  143. data/lib/concurrent/utility/processor_count.rb +0 -152
  144. data/lib/extension_helper.rb +0 -37
@@ -57,28 +57,31 @@ module Concurrent
57
57
  end
58
58
 
59
59
  # Run a block that reads and writes `TVar`s as a single atomic transaction.
60
- # With respect to the value of `TVar` objects, the transaction is atomic,
61
- # in that it either happens or it does not, consistent, in that the `TVar`
60
+ # With respect to the value of `TVar` objects, the transaction is atomic, in
61
+ # that it either happens or it does not, consistent, in that the `TVar`
62
62
  # objects involved will never enter an illegal state, and isolated, in that
63
63
  # transactions never interfere with each other. You may recognise these
64
64
  # properties from database transactions.
65
- #
65
+ #
66
66
  # There are some very important and unusual semantics that you must be aware of:
67
- #
68
- # * Most importantly, the block that you pass to atomically may be executed more than once. In most cases your code should be free of side-effects, except for via TVar.
69
- #
70
- # * If an exception escapes an atomically block it will abort the transaction.
71
- #
72
- # * It is undefined behaviour to use callcc or Fiber with atomically.
73
- #
74
- # * If you create a new thread within an atomically, it will not be part of the transaction. Creating a thread counts as a side-effect.
75
- #
67
+ #
68
+ # * Most importantly, the block that you pass to atomically may be executed
69
+ # more than once. In most cases your code should be free of
70
+ # side-effects, except for via TVar.
71
+ #
72
+ # * If an exception escapes an atomically block it will abort the transaction.
73
+ #
74
+ # * It is undefined behaviour to use callcc or Fiber with atomically.
75
+ #
76
+ # * If you create a new thread within an atomically, it will not be part of
77
+ # the transaction. Creating a thread counts as a side-effect.
78
+ #
76
79
  # Transactions within transactions are flattened to a single transaction.
77
- #
80
+ #
78
81
  # @example
79
82
  # a = new TVar(100_000)
80
83
  # b = new TVar(100)
81
- #
84
+ #
82
85
  # Concurrent::atomically do
83
86
  # a.value -= 10
84
87
  # b.value += 10
@@ -112,6 +115,9 @@ module Concurrent
112
115
  rescue Transaction::AbortError => e
113
116
  transaction.abort
114
117
  result = Transaction::ABORTED
118
+ rescue Transaction::LeaveError => e
119
+ transaction.abort
120
+ break result
115
121
  rescue => e
116
122
  transaction.abort
117
123
  raise e
@@ -141,7 +147,12 @@ module Concurrent
141
147
  raise Transaction::AbortError.new
142
148
  end
143
149
 
144
- module_function :atomically, :abort_transaction
150
+ # Leave a transaction without commiting or aborting - see `Concurrent::atomically`.
151
+ def leave_transaction
152
+ raise Transaction::LeaveError.new
153
+ end
154
+
155
+ module_function :atomically, :abort_transaction, :leave_transaction
145
156
 
146
157
  private
147
158
 
@@ -150,26 +161,30 @@ module Concurrent
150
161
  ABORTED = Object.new
151
162
 
152
163
  ReadLogEntry = Struct.new(:tvar, :version)
153
- UndoLogEntry = Struct.new(:tvar, :value)
154
164
 
155
165
  AbortError = Class.new(StandardError)
166
+ LeaveError = Class.new(StandardError)
156
167
 
157
168
  def initialize
158
- @write_set = Set.new
159
169
  @read_log = []
160
- @undo_log = []
170
+ @write_log = {}
161
171
  end
162
172
 
163
173
  def read(tvar)
164
174
  Concurrent::abort_transaction unless valid?
165
- @read_log.push(ReadLogEntry.new(tvar, tvar.unsafe_version))
166
- tvar.unsafe_value
175
+
176
+ if @write_log.has_key? tvar
177
+ @write_log[tvar]
178
+ else
179
+ @read_log.push(ReadLogEntry.new(tvar, tvar.unsafe_version))
180
+ tvar.unsafe_value
181
+ end
167
182
  end
168
183
 
169
184
  def write(tvar, value)
170
185
  # Have we already written to this TVar?
171
186
 
172
- unless @write_set.include? tvar
187
+ unless @write_log.has_key? tvar
173
188
  # Try to lock the TVar
174
189
 
175
190
  unless tvar.unsafe_lock.try_lock
@@ -177,10 +192,6 @@ module Concurrent
177
192
  Concurrent::abort_transaction
178
193
  end
179
194
 
180
- # We've locked it - add it to the write set
181
-
182
- @write_set.add(tvar)
183
-
184
195
  # If we previously wrote to it, check the version hasn't changed
185
196
 
186
197
  @read_log.each do |log_entry|
@@ -190,27 +201,20 @@ module Concurrent
190
201
  end
191
202
  end
192
203
 
193
- # Record the current value of the TVar so we can undo it later
204
+ # Record the value written
194
205
 
195
- @undo_log.push(UndoLogEntry.new(tvar, tvar.unsafe_value))
196
-
197
- # Write the new value to the TVar
198
-
199
- tvar.unsafe_value = value
206
+ @write_log[tvar] = value
200
207
  end
201
208
 
202
209
  def abort
203
- @undo_log.each do |entry|
204
- entry.tvar.unsafe_value = entry.value
205
- end
206
-
207
210
  unlock
208
211
  end
209
212
 
210
213
  def commit
211
214
  return false unless valid?
212
215
 
213
- @write_set.each do |tvar|
216
+ @write_log.each_pair do |tvar, value|
217
+ tvar.unsafe_value = value
214
218
  tvar.unsafe_increment_version
215
219
  end
216
220
 
@@ -221,7 +225,7 @@ module Concurrent
221
225
 
222
226
  def valid?
223
227
  @read_log.each do |log_entry|
224
- unless @write_set.include? log_entry.tvar
228
+ unless @write_log.has_key? log_entry.tvar
225
229
  if log_entry.tvar.unsafe_version > log_entry.version
226
230
  return false
227
231
  end
@@ -232,7 +236,7 @@ module Concurrent
232
236
  end
233
237
 
234
238
  def unlock
235
- @write_set.each do |tvar|
239
+ @write_log.each_key do |tvar|
236
240
  tvar.unsafe_lock.unlock
237
241
  end
238
242
  end
@@ -1,3 +1,5 @@
1
- require 'concurrent/utility/processor_count'
1
+ # DEPRECATED Remove this file at v1.0
2
+ require 'concurrent/utility/monotonic_time'
3
+ require 'concurrent/utility/processor_counter'
2
4
  require 'concurrent/utility/timeout'
3
5
  require 'concurrent/utility/timer'
@@ -0,0 +1,97 @@
1
+ require 'concurrent/concern/logging'
2
+ require 'concurrent/synchronization'
3
+
4
+ module Concurrent
5
+
6
+ # Provides ability to add and remove handlers to be run at `Kernel#at_exit`, order is undefined.
7
+ # Each handler is executed at most once.
8
+ #
9
+ # @!visibility private
10
+ class AtExitImplementation < Synchronization::Object
11
+ include Concern::Logging
12
+
13
+ def initialize(*args)
14
+ super()
15
+ synchronize { ns_initialize *args }
16
+ end
17
+
18
+ # Add a handler to be run at `Kernel#at_exit`
19
+ # @param [Object] handler_id optionally provide an id, if allready present, handler is replaced
20
+ # @yield the handler
21
+ # @return id of the handler
22
+ def add(handler_id = nil, &handler)
23
+ id = handler_id || handler.object_id
24
+ synchronize { @handlers[id] = handler }
25
+ id
26
+ end
27
+
28
+ # Delete a handler by handler_id
29
+ # @return [true, false]
30
+ def delete(handler_id)
31
+ !!synchronize { @handlers.delete handler_id }
32
+ end
33
+
34
+ # Is handler with handler_id rpesent?
35
+ # @return [true, false]
36
+ def handler?(handler_id)
37
+ synchronize { @handlers.key? handler_id }
38
+ end
39
+
40
+ # @return copy of the handlers
41
+ def handlers
42
+ synchronize { @handlers }.clone
43
+ end
44
+
45
+ # install `Kernel#at_exit` callback to execute added handlers
46
+ def install
47
+ synchronize do
48
+ @installed ||= begin
49
+ at_exit { runner }
50
+ true
51
+ end
52
+ self
53
+ end
54
+ end
55
+
56
+ # Will it run during `Kernel#at_exit`
57
+ def enabled?
58
+ synchronize { @enabled }
59
+ end
60
+
61
+ # Configure if it runs during `Kernel#at_exit`
62
+ def enabled=(value)
63
+ synchronize { @enabled = value }
64
+ end
65
+
66
+ # run the handlers manually
67
+ # @return ids of the handlers
68
+ def run
69
+ handlers, _ = synchronize { handlers, @handlers = @handlers, {} }
70
+ handlers.each do |_, handler|
71
+ begin
72
+ handler.call
73
+ rescue => error
74
+ log ERROR, error
75
+ end
76
+ end
77
+ handlers.keys
78
+ end
79
+
80
+ private
81
+
82
+ def ns_initialize(enabled = true)
83
+ @handlers = {}
84
+ @enabled = enabled
85
+ end
86
+
87
+ def runner
88
+ run if synchronize { @enabled }
89
+ end
90
+ end
91
+
92
+ private_constant :AtExitImplementation
93
+
94
+ # @see AtExitImplementation
95
+ # @!visibility private
96
+ AtExit = AtExitImplementation.new.install
97
+ end
@@ -0,0 +1,40 @@
1
+ module Concurrent
2
+ module Utility
3
+
4
+ # @!visibility private
5
+ module EngineDetector
6
+ def on_jruby?
7
+ ruby_engine == 'jruby'
8
+ end
9
+
10
+ def on_jruby_9000?
11
+ on_jruby? && 0 == (JRUBY_VERSION =~ /^9\.0\.0\.0/)
12
+ end
13
+
14
+ def on_cruby?
15
+ ruby_engine == 'ruby'
16
+ end
17
+
18
+ def on_rbx?
19
+ ruby_engine == 'rbx'
20
+ end
21
+
22
+ def ruby_engine
23
+ defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
24
+ end
25
+
26
+ def ruby_version(comparison, major, minor = 0, patch = 0)
27
+ result = (RUBY_VERSION.split('.').map(&:to_i) <=> [major, minor, patch])
28
+ comparisons = { :== => [0],
29
+ :>= => [1, 0],
30
+ :<= => [-1, 0],
31
+ :> => [1],
32
+ :< => [-1] }
33
+ comparisons.fetch(comparison).include? result
34
+ end
35
+ end
36
+ end
37
+
38
+ # @!visibility private
39
+ extend Utility::EngineDetector
40
+ end
@@ -0,0 +1,59 @@
1
+ require 'concurrent/synchronization'
2
+
3
+ module Concurrent
4
+
5
+ class_definition = Class.new(Synchronization::Object) do
6
+ def initialize
7
+ super()
8
+ @last_time = Time.now.to_f
9
+ ensure_ivar_visibility!
10
+ end
11
+
12
+ if defined?(Process::CLOCK_MONOTONIC)
13
+ # @!visibility private
14
+ def get_time
15
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
16
+ end
17
+ elsif Concurrent.on_jruby?
18
+ # @!visibility private
19
+ def get_time
20
+ java.lang.System.nanoTime() / 1_000_000_000.0
21
+ end
22
+ else
23
+
24
+ # @!visibility private
25
+ def get_time
26
+ synchronize do
27
+ now = Time.now.to_f
28
+ if @last_time < now
29
+ @last_time = now
30
+ else # clock has moved back in time
31
+ @last_time += 0.000_001
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ # Clock that cannot be set and represents monotonic time since
40
+ # some unspecified starting point.
41
+ #
42
+ # @!visibility private
43
+ GLOBAL_MONOTONIC_CLOCK = class_definition.new
44
+ private_constant :GLOBAL_MONOTONIC_CLOCK
45
+
46
+ # @!macro [attach] monotonic_get_time
47
+ #
48
+ # Returns the current time a tracked by the application monotonic clock.
49
+ #
50
+ # @return [Float] The current monotonic time when `since` not given else
51
+ # the elapsed monotonic time between `since` and the current time
52
+ #
53
+ # @!macro monotonic_clock_warning
54
+ def monotonic_time
55
+ GLOBAL_MONOTONIC_CLOCK.get_time
56
+ end
57
+
58
+ module_function :monotonic_time
59
+ end
@@ -0,0 +1,56 @@
1
+ require 'concurrent/synchronization/abstract_object' # for JRuby
2
+ require 'concurrent/utility/engine'
3
+
4
+ module Concurrent
5
+ module Utility
6
+
7
+ # @!visibility private
8
+ module NativeExtensionLoader
9
+
10
+ @c_ext_loaded ||= false
11
+ @java_ext_loaded ||= false
12
+
13
+ # @!visibility private
14
+ def allow_c_extensions?
15
+ Concurrent.on_cruby?
16
+ end
17
+
18
+ if Concurrent.on_cruby? && !@c_ext_loaded
19
+ tries = [
20
+ lambda do
21
+ require 'concurrent/extension'
22
+ @c_ext_loaded = true
23
+ end,
24
+ lambda do
25
+ # may be a Windows cross-compiled native gem
26
+ require "concurrent/#{RUBY_VERSION[0..2]}/extension"
27
+ @c_ext_loaded = true
28
+ end,
29
+ lambda do
30
+ warn 'Performance on MRI may be improved with the concurrent-ruby-ext gem. Please see http://concurrent-ruby.com'
31
+ end]
32
+
33
+ tries.each do |try|
34
+ begin
35
+ try.call
36
+ break
37
+ rescue LoadError
38
+ next
39
+ end
40
+ end
41
+ end
42
+
43
+ if Concurrent.on_jruby? && !@java_ext_loaded
44
+ begin
45
+ require 'concurrent_ruby_ext'
46
+ @java_ext_loaded = true
47
+ rescue LoadError
48
+ warn 'Performance on JRuby may be improved by installing the pre-compiled Java extensions. Please see http://concurrent-ruby.com'
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # @!visibility private
55
+ extend Utility::NativeExtensionLoader
56
+ end
@@ -0,0 +1,156 @@
1
+ require 'rbconfig'
2
+ require 'concurrent/delay'
3
+
4
+ module Concurrent
5
+ module Utility
6
+
7
+ # @!visibility private
8
+ class ProcessorCounter
9
+ def initialize
10
+ @processor_count = Delay.new { compute_processor_count }
11
+ @physical_processor_count = Delay.new { compute_physical_processor_count }
12
+ end
13
+
14
+ # Number of processors seen by the OS and used for process scheduling. For
15
+ # performance reasons the calculated value will be memoized on the first
16
+ # call.
17
+ #
18
+ # When running under JRuby the Java runtime call
19
+ # `java.lang.Runtime.getRuntime.availableProcessors` will be used. According
20
+ # to the Java documentation this "value may change during a particular
21
+ # invocation of the virtual machine... [applications] should therefore
22
+ # occasionally poll this property." Subsequently the result will NOT be
23
+ # memoized under JRuby.
24
+ #
25
+ # On Windows the Win32 API will be queried for the
26
+ # `NumberOfLogicalProcessors from Win32_Processor`. This will return the
27
+ # total number "logical processors for the current instance of the
28
+ # processor", which taked into account hyperthreading.
29
+ #
30
+ # * AIX: /usr/sbin/pmcycles (AIX 5+), /usr/sbin/lsdev
31
+ # * BSD: /sbin/sysctl
32
+ # * Cygwin: /proc/cpuinfo
33
+ # * Darwin: /usr/bin/hwprefs, /usr/sbin/sysctl
34
+ # * HP-UX: /usr/sbin/ioscan
35
+ # * IRIX: /usr/sbin/sysconf
36
+ # * Linux: /proc/cpuinfo
37
+ # * Minix 3+: /proc/cpuinfo
38
+ # * Solaris: /usr/sbin/psrinfo
39
+ # * Tru64 UNIX: /usr/sbin/psrinfo
40
+ # * UnixWare: /usr/sbin/psrinfo
41
+ #
42
+ # @return [Integer] number of processors seen by the OS or Java runtime
43
+ #
44
+ # @see https://github.com/grosser/parallel/blob/4fc8b89d08c7091fe0419ca8fba1ec3ce5a8d185/lib/parallel.rb
45
+ #
46
+ # @see http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html#availableProcessors()
47
+ # @see http://msdn.microsoft.com/en-us/library/aa394373(v=vs.85).aspx
48
+ def processor_count
49
+ @processor_count.value
50
+ end
51
+
52
+ # Number of physical processor cores on the current system. For performance
53
+ # reasons the calculated value will be memoized on the first call.
54
+ #
55
+ # On Windows the Win32 API will be queried for the `NumberOfCores from
56
+ # Win32_Processor`. This will return the total number "of cores for the
57
+ # current instance of the processor." On Unix-like operating systems either
58
+ # the `hwprefs` or `sysctl` utility will be called in a subshell and the
59
+ # returned value will be used. In the rare case where none of these methods
60
+ # work or an exception is raised the function will simply return 1.
61
+ #
62
+ # @return [Integer] number physical processor cores on the current system
63
+ #
64
+ # @see https://github.com/grosser/parallel/blob/4fc8b89d08c7091fe0419ca8fba1ec3ce5a8d185/lib/parallel.rb
65
+ #
66
+ # @see http://msdn.microsoft.com/en-us/library/aa394373(v=vs.85).aspx
67
+ # @see http://www.unix.com/man-page/osx/1/HWPREFS/
68
+ # @see http://linux.die.net/man/8/sysctl
69
+ def physical_processor_count
70
+ @physical_processor_count.value
71
+ end
72
+
73
+ private
74
+
75
+ def compute_processor_count
76
+ if Concurrent.on_jruby?
77
+ java.lang.Runtime.getRuntime.availableProcessors
78
+ else
79
+ os_name = RbConfig::CONFIG["target_os"]
80
+ if os_name =~ /mingw|mswin/
81
+ require 'win32ole'
82
+ result = WIN32OLE.connect("winmgmts://").ExecQuery(
83
+ "select NumberOfLogicalProcessors from Win32_Processor")
84
+ result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
85
+ elsif File.readable?("/proc/cpuinfo")
86
+ IO.read("/proc/cpuinfo").scan(/^processor/).size
87
+ elsif File.executable?("/usr/bin/hwprefs")
88
+ IO.popen("/usr/bin/hwprefs thread_count").read.to_i
89
+ elsif File.executable?("/usr/sbin/psrinfo")
90
+ IO.popen("/usr/sbin/psrinfo").read.scan(/^.*on-*line/).size
91
+ elsif File.executable?("/usr/sbin/ioscan")
92
+ IO.popen("/usr/sbin/ioscan -kC processor") do |out|
93
+ out.read.scan(/^.*processor/).size
94
+ end
95
+ elsif File.executable?("/usr/sbin/pmcycles")
96
+ IO.popen("/usr/sbin/pmcycles -m").read.count("\n")
97
+ elsif File.executable?("/usr/sbin/lsdev")
98
+ IO.popen("/usr/sbin/lsdev -Cc processor -S 1").read.count("\n")
99
+ elsif File.executable?("/usr/sbin/sysconf") and os_name =~ /irix/i
100
+ IO.popen("/usr/sbin/sysconf NPROC_ONLN").read.to_i
101
+ elsif File.executable?("/usr/sbin/sysctl")
102
+ IO.popen("/usr/sbin/sysctl -n hw.ncpu").read.to_i
103
+ elsif File.executable?("/sbin/sysctl")
104
+ IO.popen("/sbin/sysctl -n hw.ncpu").read.to_i
105
+ else
106
+ 1
107
+ end
108
+ end
109
+ rescue
110
+ return 1
111
+ end
112
+
113
+ def compute_physical_processor_count
114
+ ppc = case RbConfig::CONFIG["target_os"]
115
+ when /darwin1/
116
+ IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
117
+ when /linux/
118
+ cores = {} # unique physical ID / core ID combinations
119
+ phy = 0
120
+ IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
121
+ if ln.start_with?("physical")
122
+ phy = ln[/\d+/]
123
+ elsif ln.start_with?("core")
124
+ cid = phy + ":" + ln[/\d+/]
125
+ cores[cid] = true if not cores[cid]
126
+ end
127
+ end
128
+ cores.count
129
+ when /mswin|mingw/
130
+ require 'win32ole'
131
+ result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
132
+ "select NumberOfCores from Win32_Processor")
133
+ result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
134
+ else
135
+ processor_count
136
+ end
137
+ # fall back to logical count if physical info is invalid
138
+ ppc > 0 ? ppc : processor_count
139
+ rescue
140
+ return 1
141
+ end
142
+ end
143
+ end
144
+
145
+ # create the default ProcessorCounter on load
146
+ @processor_counter = Utility::ProcessorCounter.new
147
+ singleton_class.send :attr_reader, :processor_counter
148
+
149
+ def self.processor_count
150
+ processor_counter.processor_count
151
+ end
152
+
153
+ def self.physical_processor_count
154
+ processor_counter.physical_processor_count
155
+ end
156
+ end