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
@@ -1,168 +1,121 @@
1
- if RUBY_PLATFORM == 'java'
2
- require_relative 'executor'
1
+ if Concurrent.on_jruby?
2
+
3
+ require 'concurrent/executor/executor_service'
3
4
 
4
5
  module Concurrent
5
6
 
6
7
  # @!macro thread_pool_executor
7
- class JavaThreadPoolExecutor
8
- include JavaExecutor
8
+ # @!macro thread_pool_options
9
+ # @!visibility private
10
+ class JavaThreadPoolExecutor < JavaExecutorService
9
11
 
10
- # Default maximum number of threads that will be created in the pool.
12
+ # @!macro thread_pool_executor_constant_default_max_pool_size
11
13
  DEFAULT_MAX_POOL_SIZE = java.lang.Integer::MAX_VALUE # 2147483647
12
14
 
13
- # Default minimum number of threads that will be retained in the pool.
15
+ # @!macro thread_pool_executor_constant_default_min_pool_size
14
16
  DEFAULT_MIN_POOL_SIZE = 0
15
17
 
16
- # Default maximum number of tasks that may be added to the task queue.
18
+ # @!macro thread_pool_executor_constant_default_max_queue_size
17
19
  DEFAULT_MAX_QUEUE_SIZE = 0
18
20
 
19
- # Default maximum number of seconds a thread in the pool may remain idle
20
- # before being reclaimed.
21
+ # @!macro thread_pool_executor_constant_default_thread_timeout
21
22
  DEFAULT_THREAD_IDLETIMEOUT = 60
22
23
 
23
- # The maximum number of threads that may be created in the pool.
24
+ # @!macro thread_pool_executor_attr_reader_max_length
24
25
  attr_reader :max_length
25
26
 
26
- # The maximum number of tasks that may be waiting in the work queue at any one time.
27
- # When the queue size reaches `max_queue` subsequent tasks will be rejected in
28
- # accordance with the configured `fallback_policy`.
27
+ # @!macro thread_pool_executor_attr_reader_max_queue
29
28
  attr_reader :max_queue
30
29
 
31
- # Create a new thread pool.
32
- #
33
- # @param [Hash] opts the options which configure the thread pool
34
- #
35
- # @option opts [Integer] :max_threads (DEFAULT_MAX_POOL_SIZE) the maximum
36
- # number of threads to be created
37
- # @option opts [Integer] :min_threads (DEFAULT_MIN_POOL_SIZE) the minimum
38
- # number of threads to be retained
39
- # @option opts [Integer] :idletime (DEFAULT_THREAD_IDLETIMEOUT) the maximum
40
- # number of seconds a thread may be idle before being reclaimed
41
- # @option opts [Integer] :max_queue (DEFAULT_MAX_QUEUE_SIZE) the maximum
42
- # number of tasks allowed in the work queue at any one time; a value of
43
- # zero means the queue may grow without bound
44
- # @option opts [Symbol] :fallback_policy (:abort) the policy for handling new
45
- # tasks that are received when the queue size has reached
46
- # `max_queue` or the executir has shut down
47
- #
48
- # @raise [ArgumentError] if `:max_threads` is less than one
49
- # @raise [ArgumentError] if `:min_threads` is less than zero
50
- # @raise [ArgumentError] if `:fallback_policy` is not one of the values specified
51
- # in `FALLBACK_POLICIES`
52
- #
53
- # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
30
+ # @!macro thread_pool_executor_method_initialize
54
31
  def initialize(opts = {})
55
- min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
56
- max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
57
- idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
58
- @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
59
- @fallback_policy = opts.fetch(:fallback_policy, opts.fetch(:overflow_policy, :abort))
60
- warn '[DEPRECATED] :overflow_policy is deprecated terminology, please use :fallback_policy instead' if opts.has_key?(:overflow_policy)
61
-
62
- raise ArgumentError.new('max_threads must be greater than zero') if max_length <= 0
63
- raise ArgumentError.new('min_threads cannot be less than zero') if min_length < 0
64
- raise ArgumentError.new('min_threads cannot be more than max_threads') if min_length > max_length
65
- raise ArgumentError.new("#{fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
66
-
67
- if @max_queue == 0
68
- queue = java.util.concurrent.LinkedBlockingQueue.new
69
- else
70
- queue = java.util.concurrent.LinkedBlockingQueue.new(@max_queue)
71
- end
72
-
73
- @executor = java.util.concurrent.ThreadPoolExecutor.new(
74
- min_length, max_length,
75
- idletime, java.util.concurrent.TimeUnit::SECONDS,
76
- queue, FALLBACK_POLICIES[@fallback_policy].new)
77
-
78
- set_shutdown_hook
32
+ super(opts)
79
33
  end
80
34
 
81
- # @!macro executor_module_method_can_overflow_question
35
+ # @!macro executor_service_method_can_overflow_question
82
36
  def can_overflow?
83
37
  @max_queue != 0
84
38
  end
85
39
 
86
- # The minimum number of threads that may be retained in the pool.
87
- #
88
- # @return [Integer] the min_length
40
+ # @!macro thread_pool_executor_attr_reader_min_length
89
41
  def min_length
90
42
  @executor.getCorePoolSize
91
43
  end
92
44
 
93
- # The maximum number of threads that may be created in the pool.
94
- #
95
- # @return [Integer] the max_length
45
+ # @!macro thread_pool_executor_attr_reader_max_length
96
46
  def max_length
97
47
  @executor.getMaximumPoolSize
98
48
  end
99
49
 
100
- # The number of threads currently in the pool.
101
- #
102
- # @return [Integer] the length
50
+ # @!macro thread_pool_executor_attr_reader_length
103
51
  def length
104
52
  @executor.getPoolSize
105
53
  end
106
- alias_method :current_length, :length
107
54
 
108
- # The largest number of threads that have been created in the pool since construction.
109
- #
110
- # @return [Integer] the largest_length
55
+ # @!macro thread_pool_executor_attr_reader_largest_length
111
56
  def largest_length
112
57
  @executor.getLargestPoolSize
113
58
  end
114
59
 
115
- # The number of tasks that have been scheduled for execution on the pool since construction.
116
- #
117
- # @return [Integer] the scheduled_task_count
60
+ # @!macro thread_pool_executor_attr_reader_scheduled_task_count
118
61
  def scheduled_task_count
119
62
  @executor.getTaskCount
120
63
  end
121
64
 
122
- # The number of tasks that have been completed by the pool since construction.
123
- #
124
- # @return [Integer] the completed_task_count
65
+ # @!macro thread_pool_executor_attr_reader_completed_task_count
125
66
  def completed_task_count
126
67
  @executor.getCompletedTaskCount
127
68
  end
128
69
 
129
- # The number of seconds that a thread may be idle before being reclaimed.
130
- #
131
- # @return [Integer] the idletime
70
+ # @!macro thread_pool_executor_attr_reader_idletime
132
71
  def idletime
133
72
  @executor.getKeepAliveTime(java.util.concurrent.TimeUnit::SECONDS)
134
73
  end
135
74
 
136
- # The number of tasks in the queue awaiting execution.
137
- #
138
- # @return [Integer] the queue_length
75
+ # @!macro thread_pool_executor_attr_reader_queue_length
139
76
  def queue_length
140
77
  @executor.getQueue.size
141
78
  end
142
79
 
143
- # Number of tasks that may be enqueued before reaching `max_queue` and rejecting
144
- # new tasks. A value of -1 indicates that the queue may grow without bound.
145
- #
146
- # @return [Integer] the remaining_capacity
80
+ # @!macro thread_pool_executor_attr_reader_remaining_capacity
147
81
  def remaining_capacity
148
82
  @max_queue == 0 ? -1 : @executor.getQueue.remainingCapacity
149
83
  end
150
84
 
151
- # This method is deprecated and will be removed soon.
152
- # This method is supost to return the threads status, but Java API doesn't
153
- # provide a way to get the thread status. So we return an empty Array instead.
154
- def status
155
- warn '[DEPRECATED] `status` is deprecated and will be removed soon.'
156
- warn "Calls to `status` return an empty Array. Java ThreadPoolExecutor does not provide thread's status."
157
- []
158
- end
159
-
160
- # Is the thread pool running?
161
- #
162
- # @return [Boolean] `true` when running, `false` when shutting down or shutdown
85
+ # @!macro executor_service_method_running_question
163
86
  def running?
164
87
  super && !@executor.isTerminating
165
88
  end
89
+
90
+ protected
91
+
92
+ def ns_initialize(opts)
93
+ min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
94
+ max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
95
+ idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
96
+ @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
97
+ @fallback_policy = opts.fetch(:fallback_policy, opts.fetch(:overflow_policy, :abort))
98
+ deprecated ' :overflow_policy is deprecated terminology, please use :fallback_policy instead' if opts.has_key?(:overflow_policy)
99
+
100
+ raise ArgumentError.new("`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if max_length < DEFAULT_MIN_POOL_SIZE
101
+ raise ArgumentError.new("`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}") if max_length > DEFAULT_MAX_POOL_SIZE
102
+ raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if min_length < DEFAULT_MIN_POOL_SIZE
103
+ raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length
104
+ raise ArgumentError.new("#{fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICY_CLASSES.include?(@fallback_policy)
105
+
106
+ if @max_queue == 0
107
+ queue = java.util.concurrent.LinkedBlockingQueue.new
108
+ else
109
+ queue = java.util.concurrent.LinkedBlockingQueue.new(@max_queue)
110
+ end
111
+
112
+ @executor = java.util.concurrent.ThreadPoolExecutor.new(
113
+ min_length, max_length,
114
+ idletime, java.util.concurrent.TimeUnit::SECONDS,
115
+ queue, FALLBACK_POLICY_CLASSES[@fallback_policy].new)
116
+
117
+ self.auto_terminate = opts.fetch(:auto_terminate, true)
118
+ end
166
119
  end
167
120
  end
168
121
  end
@@ -3,27 +3,18 @@ require 'concurrent/executor/ruby_thread_pool_executor'
3
3
  module Concurrent
4
4
 
5
5
  # @!macro cached_thread_pool
6
+ # @!macro thread_pool_options
7
+ # @!macro thread_pool_executor_public_api
8
+ # @!visibility private
6
9
  class RubyCachedThreadPool < RubyThreadPoolExecutor
7
10
 
8
- # Create a new thread pool.
9
- #
10
- # @param [Hash] opts the options defining pool behavior.
11
- # @option opts [Symbol] :fallback_policy (`:abort`) the fallback policy
12
- #
13
- # @raise [ArgumentError] if `fallback_policy` is not a known policy
11
+ # @!macro cached_thread_pool_method_initialize
14
12
  def initialize(opts = {})
15
- fallback_policy = opts.fetch(:fallback_policy, opts.fetch(:overflow_policy, :abort))
16
-
17
- raise ArgumentError.new("#{fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(fallback_policy)
18
-
19
- opts = opts.merge(
20
- min_threads: 0,
21
- max_threads: DEFAULT_MAX_POOL_SIZE,
22
- fallback_policy: fallback_policy,
23
- max_queue: DEFAULT_MAX_QUEUE_SIZE,
24
- idletime: DEFAULT_THREAD_IDLETIMEOUT
25
- )
26
- super(opts)
13
+ defaults = { idletime: DEFAULT_THREAD_IDLETIMEOUT }
14
+ overrides = { min_threads: 0,
15
+ max_threads: DEFAULT_MAX_POOL_SIZE,
16
+ max_queue: DEFAULT_MAX_QUEUE_SIZE }
17
+ super(defaults.merge(opts).merge(overrides))
27
18
  end
28
19
  end
29
20
  end
@@ -3,30 +3,19 @@ require 'concurrent/executor/ruby_thread_pool_executor'
3
3
  module Concurrent
4
4
 
5
5
  # @!macro fixed_thread_pool
6
+ # @!macro thread_pool_options
7
+ # @!macro thread_pool_executor_public_api
8
+ # @!visibility private
6
9
  class RubyFixedThreadPool < RubyThreadPoolExecutor
7
10
 
8
- # Create a new thread pool.
9
- #
10
- # @param [Integer] num_threads the number of threads to allocate
11
- # @param [Hash] opts the options defining pool behavior.
12
- # @option opts [Symbol] :fallback_policy (`:abort`) the fallback policy
13
- #
14
- # @raise [ArgumentError] if `num_threads` is less than or equal to zero
15
- # @raise [ArgumentError] if `fallback_policy` is not a known policy
11
+ # @!macro fixed_thread_pool_method_initialize
16
12
  def initialize(num_threads, opts = {})
17
- fallback_policy = opts.fetch(:fallback_policy, opts.fetch(:overflow_policy, :abort))
18
-
19
- raise ArgumentError.new('number of threads must be greater than zero') if num_threads < 1
20
- raise ArgumentError.new("#{fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(fallback_policy)
21
-
22
- opts = {
23
- min_threads: num_threads,
24
- max_threads: num_threads,
25
- fallback_policy: fallback_policy,
26
- max_queue: DEFAULT_MAX_QUEUE_SIZE,
27
- idletime: DEFAULT_THREAD_IDLETIMEOUT,
28
- }.merge(opts)
29
- super(opts)
13
+ raise ArgumentError.new('number of threads must be greater than zero') if num_threads.to_i < 1
14
+ defaults = { max_queue: DEFAULT_MAX_QUEUE_SIZE,
15
+ idletime: DEFAULT_THREAD_IDLETIMEOUT }
16
+ overrides = { min_threads: num_threads,
17
+ max_threads: num_threads }
18
+ super(defaults.merge(opts).merge(overrides))
30
19
  end
31
20
  end
32
21
  end
@@ -1,31 +1,29 @@
1
- require_relative 'executor'
1
+ require 'concurrent/executor/executor_service'
2
2
 
3
3
  module Concurrent
4
4
 
5
5
  # @!macro single_thread_executor
6
- class RubySingleThreadExecutor
7
- include RubyExecutor
8
- include SerialExecutor
6
+ # @!macro thread_pool_options
7
+ # @!macro abstract_executor_service_public_api
8
+ # @!visibility private
9
+ class RubySingleThreadExecutor < RubyExecutorService
10
+ include SerialExecutorService
9
11
 
10
- # Create a new thread pool.
11
- #
12
- # @option opts [Symbol] :fallback_policy (:discard) the policy for
13
- # handling new tasks that are received when the queue size has
14
- # reached `max_queue` or after the executor has shut down
15
- #
16
- # @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
17
- # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html
18
- # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
12
+ # @!macro single_thread_executor_method_initialize
19
13
  def initialize(opts = {})
14
+ super
15
+ end
16
+
17
+ protected
18
+
19
+ def ns_initialize(opts)
20
20
  @queue = Queue.new
21
21
  @thread = nil
22
22
  @fallback_policy = opts.fetch(:fallback_policy, :discard)
23
23
  raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
24
- init_executor
24
+ self.auto_terminate = opts.fetch(:auto_terminate, true)
25
25
  end
26
26
 
27
- protected
28
-
29
27
  # @!visibility private
30
28
  def execute(*args, &task)
31
29
  supervise
@@ -1,261 +1,345 @@
1
1
  require 'thread'
2
2
 
3
- require_relative 'executor'
4
3
  require 'concurrent/atomic/event'
5
- require 'concurrent/executor/ruby_thread_pool_worker'
4
+ require 'concurrent/concern/logging'
5
+ require 'concurrent/executor/executor_service'
6
+ require 'concurrent/utility/monotonic_time'
6
7
 
7
8
  module Concurrent
8
9
 
9
10
  # @!macro thread_pool_executor
10
- class RubyThreadPoolExecutor
11
- include RubyExecutor
11
+ # @!macro thread_pool_options
12
+ # @!visibility private
13
+ class RubyThreadPoolExecutor < RubyExecutorService
12
14
 
13
- # Default maximum number of threads that will be created in the pool.
14
- DEFAULT_MAX_POOL_SIZE = 2**15 # 32768
15
+ # @!macro thread_pool_executor_constant_default_max_pool_size
16
+ DEFAULT_MAX_POOL_SIZE = 2_147_483_647 # java.lang.Integer::MAX_VALUE
15
17
 
16
- # Default minimum number of threads that will be retained in the pool.
18
+ # @!macro thread_pool_executor_constant_default_min_pool_size
17
19
  DEFAULT_MIN_POOL_SIZE = 0
18
20
 
19
- # Default maximum number of tasks that may be added to the task queue.
21
+ # @!macro thread_pool_executor_constant_default_max_queue_size
20
22
  DEFAULT_MAX_QUEUE_SIZE = 0
21
23
 
22
- # Default maximum number of seconds a thread in the pool may remain idle
23
- # before being reclaimed.
24
+ # @!macro thread_pool_executor_constant_default_thread_timeout
24
25
  DEFAULT_THREAD_IDLETIMEOUT = 60
25
26
 
26
- # The maximum number of threads that may be created in the pool.
27
+ # @!macro thread_pool_executor_attr_reader_max_length
27
28
  attr_reader :max_length
28
29
 
29
- # The minimum number of threads that may be retained in the pool.
30
+ # @!macro thread_pool_executor_attr_reader_min_length
30
31
  attr_reader :min_length
31
32
 
32
- # The largest number of threads that have been created in the pool since construction.
33
+ # @!macro thread_pool_executor_attr_reader_largest_length
33
34
  attr_reader :largest_length
34
35
 
35
- # The number of tasks that have been scheduled for execution on the pool since construction.
36
+ # @!macro thread_pool_executor_attr_reader_scheduled_task_count
36
37
  attr_reader :scheduled_task_count
37
38
 
38
- # The number of tasks that have been completed by the pool since construction.
39
+ # @!macro thread_pool_executor_attr_reader_completed_task_count
39
40
  attr_reader :completed_task_count
40
41
 
41
- # The number of seconds that a thread may be idle before being reclaimed.
42
+ # @!macro thread_pool_executor_attr_reader_idletime
42
43
  attr_reader :idletime
43
44
 
44
- # The maximum number of tasks that may be waiting in the work queue at any one time.
45
- # When the queue size reaches `max_queue` subsequent tasks will be rejected in
46
- # accordance with the configured `fallback_policy`.
45
+ # @!macro thread_pool_executor_attr_reader_max_queue
47
46
  attr_reader :max_queue
48
47
 
49
- # Create a new thread pool.
50
- #
51
- # @param [Hash] opts the options which configure the thread pool
52
- #
53
- # @option opts [Integer] :max_threads (DEFAULT_MAX_POOL_SIZE) the maximum
54
- # number of threads to be created
55
- # @option opts [Integer] :min_threads (DEFAULT_MIN_POOL_SIZE) the minimum
56
- # number of threads to be retained
57
- # @option opts [Integer] :idletime (DEFAULT_THREAD_IDLETIMEOUT) the maximum
58
- # number of seconds a thread may be idle before being reclaimed
59
- # @option opts [Integer] :max_queue (DEFAULT_MAX_QUEUE_SIZE) the maximum
60
- # number of tasks allowed in the work queue at any one time; a value of
61
- # zero means the queue may grow without bound
62
- # @option opts [Symbol] :fallback_policy (:abort) the policy for handling new
63
- # tasks that are received when the queue size has reached
64
- # `max_queue` or the executor has shut down
65
- #
66
- # @raise [ArgumentError] if `:max_threads` is less than one
67
- # @raise [ArgumentError] if `:min_threads` is less than zero
68
- # @raise [ArgumentError] if `:fallback_policy` is not one of the values specified
69
- # in `FALLBACK_POLICIES`
70
- #
71
- # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
48
+ # @!macro thread_pool_executor_method_initialize
72
49
  def initialize(opts = {})
73
- @min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
74
- @max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
75
- @idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
76
- @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
77
- @fallback_policy = opts.fetch(:fallback_policy, opts.fetch(:overflow_policy, :abort))
78
- warn '[DEPRECATED] :overflow_policy is deprecated terminology, please use :fallback_policy instead' if opts.has_key?(:overflow_policy)
79
-
80
- raise ArgumentError.new('max_threads must be greater than zero') if @max_length <= 0
81
- raise ArgumentError.new('min_threads cannot be less than zero') if @min_length < 0
82
- raise ArgumentError.new("#{fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
83
- raise ArgumentError.new('min_threads cannot be more than max_threads') if min_length > max_length
84
-
85
- init_executor
86
-
87
- @pool = []
88
- @queue = Queue.new
89
- @scheduled_task_count = 0
90
- @completed_task_count = 0
91
- @largest_length = 0
92
-
93
- @gc_interval = opts.fetch(:gc_interval, 1).to_i # undocumented
94
- @last_gc_time = Time.now.to_f - [1.0, (@gc_interval * 2.0)].max
50
+ super(opts)
95
51
  end
96
52
 
97
- # @!macro executor_module_method_can_overflow_question
53
+ # @!macro executor_service_method_can_overflow_question
98
54
  def can_overflow?
99
- @max_queue != 0
55
+ synchronize { ns_limited_queue? }
100
56
  end
101
57
 
102
- # The number of threads currently in the pool.
103
- #
104
- # @return [Integer] the length
58
+ # @!macro thread_pool_executor_attr_reader_length
105
59
  def length
106
- mutex.synchronize { running? ? @pool.length : 0 }
60
+ synchronize { @pool.length }
107
61
  end
108
62
 
109
- alias_method :current_length, :length
110
-
111
- # The number of tasks in the queue awaiting execution.
112
- #
113
- # @return [Integer] the queue_length
63
+ # @!macro thread_pool_executor_attr_reader_queue_length
114
64
  def queue_length
115
- mutex.synchronize { running? ? @queue.length : 0 }
65
+ synchronize { @queue.length }
116
66
  end
117
67
 
118
- # Number of tasks that may be enqueued before reaching `max_queue` and rejecting
119
- # new tasks. A value of -1 indicates that the queue may grow without bound.
120
- #
121
- # @return [Integer] the remaining_capacity
68
+ # @!macro thread_pool_executor_attr_reader_remaining_capacity
122
69
  def remaining_capacity
123
- mutex.synchronize { @max_queue == 0 ? -1 : @max_queue - @queue.length }
70
+ synchronize do
71
+ if ns_limited_queue?
72
+ @max_queue - @queue.length
73
+ else
74
+ -1
75
+ end
76
+ end
124
77
  end
125
78
 
126
- # Returns an array with the status of each thread in the pool
127
- #
128
- # This method is deprecated and will be removed soon.
129
- def status
130
- warn '[DEPRECATED] `status` is deprecated and will be removed soon.'
131
- mutex.synchronize { @pool.collect { |worker| worker.status } }
79
+ # @!visibility private
80
+ def remove_busy_worker(worker)
81
+ synchronize { ns_remove_busy_worker worker }
132
82
  end
133
83
 
134
- # Run on task completion.
135
- #
136
84
  # @!visibility private
137
- def on_end_task
138
- mutex.synchronize do
139
- @completed_task_count += 1 #if success
140
- break unless running?
141
- end
85
+ def ready_worker(worker)
86
+ synchronize { ns_ready_worker worker }
142
87
  end
143
88
 
144
- # Run when a thread worker exits.
145
- #
146
89
  # @!visibility private
147
- def on_worker_exit(worker)
148
- mutex.synchronize do
149
- @pool.delete(worker)
150
- if @pool.empty? && !running?
151
- stop_event.set
152
- stopped_event.set
153
- end
154
- end
90
+ def worker_not_old_enough(worker)
91
+ synchronize { ns_worker_not_old_enough worker }
92
+ end
93
+
94
+ # @!visibility private
95
+ def worker_died(worker)
96
+ synchronize { ns_worker_died worker }
155
97
  end
156
98
 
157
99
  protected
158
100
 
159
101
  # @!visibility private
160
- def execute(*args, &task)
161
- prune_pool
162
- if ensure_capacity?
102
+ def ns_initialize(opts)
103
+ @min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i
104
+ @max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i
105
+ @idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i
106
+ @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i
107
+ @fallback_policy = opts.fetch(:fallback_policy, opts.fetch(:overflow_policy, :abort))
108
+ raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy)
109
+ deprecated ':overflow_policy is deprecated terminology, please use :fallback_policy instead' if opts.has_key?(:overflow_policy)
110
+
111
+ raise ArgumentError.new("`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @max_length < DEFAULT_MIN_POOL_SIZE
112
+ raise ArgumentError.new("`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}") if @max_length > DEFAULT_MAX_POOL_SIZE
113
+ raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @min_length < DEFAULT_MIN_POOL_SIZE
114
+ raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length
115
+
116
+ self.auto_terminate = opts.fetch(:auto_terminate, true)
117
+
118
+ @pool = [] # all workers
119
+ @ready = [] # used as a stash (most idle worker is at the start)
120
+ @queue = [] # used as queue
121
+ # @ready or @queue is empty at all times
122
+ @scheduled_task_count = 0
123
+ @completed_task_count = 0
124
+ @largest_length = 0
125
+
126
+ @gc_interval = opts.fetch(:gc_interval, @idletime / 2.0).to_i # undocumented
127
+ @next_gc_time = Concurrent.monotonic_time + @gc_interval
128
+ end
129
+
130
+ # @!visibility private
131
+ def ns_limited_queue?
132
+ @max_queue != 0
133
+ end
134
+
135
+ # @!visibility private
136
+ def ns_execute(*args, &task)
137
+ if ns_assign_worker(*args, &task) || ns_enqueue(*args, &task)
163
138
  @scheduled_task_count += 1
164
- @queue << [args, task]
165
139
  else
166
- handle_fallback(*args, &task) if @max_queue != 0 && @queue.length >= @max_queue
140
+ handle_fallback(*args, &task)
167
141
  end
142
+
143
+ ns_prune_pool if @next_gc_time < Concurrent.monotonic_time
144
+ # raise unless @ready.empty? || @queue.empty? # assert
168
145
  end
169
146
 
147
+ alias_method :execute, :ns_execute
148
+
170
149
  # @!visibility private
171
- def shutdown_execution
150
+ def ns_shutdown_execution
172
151
  if @pool.empty?
152
+ # nothing to do
173
153
  stopped_event.set
174
- else
175
- @pool.length.times { @queue << :stop }
176
154
  end
155
+ if @queue.empty?
156
+ # no more tasks will be accepted, just stop all workers
157
+ @pool.each(&:stop)
158
+ end
159
+
160
+ # raise unless @ready.empty? || @queue.empty? # assert
177
161
  end
178
162
 
163
+ alias_method :shutdown_execution, :ns_shutdown_execution
164
+
179
165
  # @!visibility private
180
- def kill_execution
181
- @queue.clear
182
- drain_pool
166
+ def ns_kill_execution
167
+ # TODO log out unprocessed tasks in queue
168
+ # TODO try to shutdown first?
169
+ @pool.each &:kill
170
+ @pool.clear
171
+ @ready.clear
183
172
  end
184
173
 
185
- # Check the thread pool configuration and determine if the pool
186
- # has enought capacity to handle the request. Will grow the size
187
- # of the pool if necessary.
188
- #
189
- # @return [Boolean] true if the pool has enough capacity else false
174
+ alias_method :kill_execution, :ns_kill_execution
175
+
176
+ # tries to assign task to a worker, tries to get one from @ready or to create new one
177
+ # @return [true, false] if task is assigned to a worker
190
178
  #
191
179
  # @!visibility private
192
- def ensure_capacity?
193
- additional = 0
194
- capacity = true
195
-
196
- if @pool.size < @min_length
197
- additional = @min_length - @pool.size
198
- elsif @queue.empty? && @queue.num_waiting >= 1
199
- additional = 0
200
- elsif @pool.size == 0 && @min_length == 0
201
- additional = 1
202
- elsif @pool.size < @max_length || @max_length == 0
203
- additional = 1
204
- elsif @max_queue == 0 || @queue.size < @max_queue
205
- additional = 0
180
+ def ns_assign_worker(*args, &task)
181
+ # keep growing if the pool is not at the minimum yet
182
+ worker = (@ready.pop if @pool.size >= @min_length) || ns_add_busy_worker
183
+ if worker
184
+ worker << [task, args]
185
+ true
206
186
  else
207
- capacity = false
187
+ false
208
188
  end
189
+ rescue ThreadError
190
+ # Raised when the operating system refuses to create the new thread
191
+ return false
192
+ end
209
193
 
210
- additional.times do
211
- @pool << create_worker_thread
194
+ # tries to enqueue task
195
+ # @return [true, false] if enqueued
196
+ #
197
+ # @!visibility private
198
+ def ns_enqueue(*args, &task)
199
+ if !ns_limited_queue? || @queue.size < @max_queue
200
+ @queue << [task, args]
201
+ true
202
+ else
203
+ false
212
204
  end
205
+ end
213
206
 
214
- if additional > 0
215
- @largest_length = [@largest_length, @pool.length].max
216
- end
207
+ # @!visibility private
208
+ def ns_worker_died(worker)
209
+ ns_remove_busy_worker worker
210
+ replacement_worker = ns_add_busy_worker
211
+ ns_ready_worker replacement_worker, false if replacement_worker
212
+ end
213
+
214
+ # creates new worker which has to receive work to do after it's added
215
+ # @return [nil, Worker] nil of max capacity is reached
216
+ #
217
+ # @!visibility private
218
+ def ns_add_busy_worker
219
+ return if @pool.size >= @max_length
217
220
 
218
- capacity
221
+ @pool << (worker = Worker.new(self))
222
+ @largest_length = @pool.length if @pool.length > @largest_length
223
+ worker
219
224
  end
220
225
 
221
- # Scan all threads in the pool and reclaim any that are dead or
222
- # have been idle too long. Will check the last time the pool was
223
- # pruned and only run if the configured garbage collection
224
- # interval has passed.
226
+ # handle ready worker, giving it new job or assigning back to @ready
225
227
  #
226
228
  # @!visibility private
227
- def prune_pool
228
- if Time.now.to_f - @gc_interval >= @last_gc_time
229
- @pool.delete_if { |worker| worker.dead? }
230
- # send :stop for each thread over idletime
231
- @pool.
232
- select { |worker| @idletime != 0 && Time.now.to_f - @idletime > worker.last_activity }.
233
- each { @queue << :stop }
234
- @last_gc_time = Time.now.to_f
229
+ def ns_ready_worker(worker, success = true)
230
+ @completed_task_count += 1 if success
231
+ task_and_args = @queue.shift
232
+ if task_and_args
233
+ worker << task_and_args
234
+ else
235
+ # stop workers when !running?, do not return them to @ready
236
+ if running?
237
+ @ready.push(worker)
238
+ else
239
+ worker.stop
240
+ end
235
241
  end
236
242
  end
237
243
 
238
- # Reclaim all threads in the pool.
244
+ # returns back worker to @ready which was not idle for enough time
239
245
  #
240
246
  # @!visibility private
241
- def drain_pool
242
- @pool.each { |worker| worker.kill }
243
- @pool.clear
247
+ def ns_worker_not_old_enough(worker)
248
+ # let's put workers coming from idle_test back to the start (as the oldest worker)
249
+ @ready.unshift(worker)
250
+ true
244
251
  end
245
252
 
246
- # Create a single worker thread to be added to the pool.
253
+ # removes a worker which is not in not tracked in @ready
247
254
  #
248
- # @return [Thread] the new thread.
255
+ # @!visibility private
256
+ def ns_remove_busy_worker(worker)
257
+ @pool.delete(worker)
258
+ stopped_event.set if @pool.empty? && !running?
259
+ true
260
+ end
261
+
262
+ # try oldest worker if it is idle for enough time, it's returned back at the start
249
263
  #
250
264
  # @!visibility private
251
- def create_worker_thread
252
- wrkr = RubyThreadPoolWorker.new(@queue, self)
253
- Thread.new(wrkr, self) do |worker, parent|
254
- Thread.current.abort_on_exception = false
255
- worker.run
256
- parent.on_worker_exit(worker)
265
+ def ns_prune_pool
266
+ return if @pool.size <= @min_length
267
+
268
+ last_used = @ready.shift
269
+ last_used << :idle_test if last_used
270
+
271
+ @next_gc_time = Concurrent.monotonic_time + @gc_interval
272
+ end
273
+
274
+ # @!visibility private
275
+ class Worker
276
+ include Concern::Logging
277
+
278
+ def initialize(pool)
279
+ # instance variables accessed only under pool's lock so no need to sync here again
280
+ @queue = Queue.new
281
+ @pool = pool
282
+ @thread = create_worker @queue, pool, pool.idletime
283
+ end
284
+
285
+ def <<(message)
286
+ @queue << message
287
+ end
288
+
289
+ def stop
290
+ @queue << :stop
291
+ end
292
+
293
+ def kill
294
+ @thread.kill
295
+ end
296
+
297
+ private
298
+
299
+ def create_worker(queue, pool, idletime)
300
+ Thread.new(queue, pool, idletime) do |queue, pool, idletime|
301
+ last_message = Concurrent.monotonic_time
302
+ catch(:stop) do
303
+ loop do
304
+
305
+ case message = queue.pop
306
+ when :idle_test
307
+ if (Concurrent.monotonic_time - last_message) > idletime
308
+ pool.remove_busy_worker(self)
309
+ throw :stop
310
+ else
311
+ pool.worker_not_old_enough(self)
312
+ end
313
+
314
+ when :stop
315
+ pool.remove_busy_worker(self)
316
+ throw :stop
317
+
318
+ else
319
+ task, args = message
320
+ run_task pool, task, args
321
+ last_message = Concurrent.monotonic_time
322
+
323
+ pool.ready_worker(self)
324
+ end
325
+
326
+ end
327
+ end
328
+ end
329
+ end
330
+
331
+ def run_task(pool, task, args)
332
+ task.call(*args)
333
+ rescue => ex
334
+ # let it fail
335
+ log DEBUG, ex
336
+ rescue Exception => ex
337
+ log ERROR, ex
338
+ pool.worker_died(self)
339
+ throw :stop
257
340
  end
258
- return wrkr
259
341
  end
342
+
343
+ private_constant :Worker
260
344
  end
261
345
  end