concurrent-ruby 0.8.0.pre2-java → 0.9.0-java

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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +114 -3
  3. data/README.md +111 -55
  4. data/lib/concurrent.rb +90 -14
  5. data/lib/concurrent/async.rb +143 -51
  6. data/lib/concurrent/atom.rb +131 -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 +34 -3
  19. data/lib/concurrent/atomic_reference/jruby.rb +6 -3
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
  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 +34 -0
  29. data/lib/concurrent/concern/dereferenceable.rb +88 -0
  30. data/lib/concurrent/concern/logging.rb +27 -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 +234 -109
  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 +19 -7
  38. data/lib/concurrent/exchanger.rb +25 -1
  39. data/lib/concurrent/executor/cached_thread_pool.rb +51 -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 +196 -23
  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_single_thread_executor.rb +17 -16
  46. data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
  47. data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
  48. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
  49. data/lib/concurrent/executor/safe_task_executor.rb +5 -4
  50. data/lib/concurrent/executor/serialized_execution.rb +22 -18
  51. data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
  52. data/lib/concurrent/executor/single_thread_executor.rb +32 -21
  53. data/lib/concurrent/executor/thread_pool_executor.rb +73 -60
  54. data/lib/concurrent/executor/timer_set.rb +96 -84
  55. data/lib/concurrent/executors.rb +1 -1
  56. data/lib/concurrent/future.rb +71 -38
  57. data/lib/concurrent/immutable_struct.rb +89 -0
  58. data/lib/concurrent/ivar.rb +152 -60
  59. data/lib/concurrent/lazy_register.rb +40 -20
  60. data/lib/concurrent/maybe.rb +226 -0
  61. data/lib/concurrent/mutable_struct.rb +227 -0
  62. data/lib/concurrent/mvar.rb +44 -43
  63. data/lib/concurrent/promise.rb +229 -136
  64. data/lib/concurrent/scheduled_task.rb +341 -43
  65. data/lib/concurrent/settable_struct.rb +127 -0
  66. data/lib/concurrent/synchronization.rb +17 -0
  67. data/lib/concurrent/synchronization/abstract_object.rb +163 -0
  68. data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
  69. data/lib/concurrent/synchronization/condition.rb +53 -0
  70. data/lib/concurrent/synchronization/java_object.rb +34 -0
  71. data/lib/concurrent/synchronization/lock.rb +32 -0
  72. data/lib/concurrent/synchronization/monitor_object.rb +26 -0
  73. data/lib/concurrent/synchronization/mutex_object.rb +43 -0
  74. data/lib/concurrent/synchronization/object.rb +78 -0
  75. data/lib/concurrent/synchronization/rbx_object.rb +75 -0
  76. data/lib/concurrent/timer_task.rb +92 -103
  77. data/lib/concurrent/tvar.rb +42 -38
  78. data/lib/concurrent/utilities.rb +3 -1
  79. data/lib/concurrent/utility/at_exit.rb +97 -0
  80. data/lib/concurrent/utility/engine.rb +44 -0
  81. data/lib/concurrent/utility/monotonic_time.rb +59 -0
  82. data/lib/concurrent/utility/native_extension_loader.rb +56 -0
  83. data/lib/concurrent/utility/processor_counter.rb +156 -0
  84. data/lib/concurrent/utility/timeout.rb +18 -14
  85. data/lib/concurrent/utility/timer.rb +11 -6
  86. data/lib/concurrent/version.rb +2 -1
  87. data/lib/concurrent_ruby.rb +1 -0
  88. data/lib/concurrent_ruby_ext.jar +0 -0
  89. metadata +46 -66
  90. data/lib/concurrent/actor.rb +0 -103
  91. data/lib/concurrent/actor/behaviour.rb +0 -70
  92. data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
  93. data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
  94. data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
  95. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
  96. data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
  97. data/lib/concurrent/actor/behaviour/linking.rb +0 -45
  98. data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
  99. data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
  100. data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
  101. data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
  102. data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
  103. data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
  104. data/lib/concurrent/actor/behaviour/termination.rb +0 -54
  105. data/lib/concurrent/actor/context.rb +0 -154
  106. data/lib/concurrent/actor/core.rb +0 -217
  107. data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
  108. data/lib/concurrent/actor/envelope.rb +0 -41
  109. data/lib/concurrent/actor/errors.rb +0 -27
  110. data/lib/concurrent/actor/internal_delegations.rb +0 -49
  111. data/lib/concurrent/actor/public_delegations.rb +0 -40
  112. data/lib/concurrent/actor/reference.rb +0 -81
  113. data/lib/concurrent/actor/root.rb +0 -37
  114. data/lib/concurrent/actor/type_check.rb +0 -48
  115. data/lib/concurrent/actor/utils.rb +0 -10
  116. data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
  117. data/lib/concurrent/actor/utils/balancer.rb +0 -42
  118. data/lib/concurrent/actor/utils/broadcast.rb +0 -52
  119. data/lib/concurrent/actor/utils/pool.rb +0 -59
  120. data/lib/concurrent/actress.rb +0 -3
  121. data/lib/concurrent/agent.rb +0 -209
  122. data/lib/concurrent/atomic.rb +0 -92
  123. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
  124. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
  125. data/lib/concurrent/atomic/synchronization.rb +0 -51
  126. data/lib/concurrent/channel/buffered_channel.rb +0 -85
  127. data/lib/concurrent/channel/channel.rb +0 -41
  128. data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
  129. data/lib/concurrent/channel/waitable_list.rb +0 -40
  130. data/lib/concurrent/channels.rb +0 -5
  131. data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
  132. data/lib/concurrent/collection/ring_buffer.rb +0 -59
  133. data/lib/concurrent/collections.rb +0 -3
  134. data/lib/concurrent/dereferenceable.rb +0 -108
  135. data/lib/concurrent/executor/java_cached_thread_pool.rb +0 -32
  136. data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
  137. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
  138. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
  139. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
  140. data/lib/concurrent/logging.rb +0 -20
  141. data/lib/concurrent/obligation.rb +0 -171
  142. data/lib/concurrent/observable.rb +0 -73
  143. data/lib/concurrent/options_parser.rb +0 -48
  144. data/lib/concurrent/utility/processor_count.rb +0 -152
  145. data/lib/extension_helper.rb +0 -37
@@ -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