concurrent-ruby 1.1.8 → 1.1.10
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/Gemfile +2 -7
- data/README.md +40 -20
- data/Rakefile +26 -31
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +0 -0
- data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +52 -22
- data/lib/concurrent-ruby/concurrent/async.rb +1 -0
- data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +1 -0
- data/lib/concurrent-ruby/concurrent/atomic/event.rb +2 -2
- data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +18 -2
- data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +4 -6
- data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +26 -5
- data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
- data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +11 -1
- data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
- data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +16 -13
- data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +13 -3
- data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +1 -1
- data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +4 -0
- data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +10 -4
- data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +26 -37
- data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +5 -5
- data/lib/concurrent-ruby/concurrent/map.rb +13 -4
- data/lib/concurrent-ruby/concurrent/promise.rb +1 -0
- data/lib/concurrent-ruby/concurrent/scheduled_task.rb +29 -16
- data/lib/concurrent-ruby/concurrent/set.rb +14 -6
- data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +1 -3
- data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +12 -0
- data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +6 -0
- data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +26 -1
- data/lib/concurrent-ruby/concurrent/timer_task.rb +11 -33
- data/lib/concurrent-ruby/concurrent/tvar.rb +20 -60
- data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +67 -35
- data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +2 -35
- data/lib/concurrent-ruby/concurrent/version.rb +1 -1
- data/lib/concurrent-ruby/concurrent-ruby.rb +5 -1
- metadata +8 -7
|
@@ -75,28 +75,31 @@ module Concurrent
|
|
|
75
75
|
|
|
76
76
|
private
|
|
77
77
|
|
|
78
|
-
#
|
|
79
|
-
# reaches `max_queue`.
|
|
78
|
+
# Returns an action which executes the `fallback_policy` once the queue
|
|
79
|
+
# size reaches `max_queue`. The reason for the indirection of an action
|
|
80
|
+
# is so that the work can be deferred outside of synchronization.
|
|
80
81
|
#
|
|
81
82
|
# @param [Array] args the arguments to the task which is being handled.
|
|
82
83
|
#
|
|
83
84
|
# @!visibility private
|
|
84
|
-
def
|
|
85
|
+
def fallback_action(*args)
|
|
85
86
|
case fallback_policy
|
|
86
87
|
when :abort
|
|
87
|
-
raise RejectedExecutionError
|
|
88
|
+
lambda { raise RejectedExecutionError }
|
|
88
89
|
when :discard
|
|
89
|
-
false
|
|
90
|
+
lambda { false }
|
|
90
91
|
when :caller_runs
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
lambda {
|
|
93
|
+
begin
|
|
94
|
+
yield(*args)
|
|
95
|
+
rescue => ex
|
|
96
|
+
# let it fail
|
|
97
|
+
log DEBUG, ex
|
|
98
|
+
end
|
|
99
|
+
true
|
|
100
|
+
}
|
|
98
101
|
else
|
|
99
|
-
fail "Unknown fallback policy #{fallback_policy}"
|
|
102
|
+
lambda { fail "Unknown fallback policy #{fallback_policy}" }
|
|
100
103
|
end
|
|
101
104
|
end
|
|
102
105
|
|
|
@@ -71,9 +71,16 @@ module Concurrent
|
|
|
71
71
|
# @return [Integer] Number of tasks that may be enqueued before reaching `max_queue` and rejecting
|
|
72
72
|
# new tasks. A value of -1 indicates that the queue may grow without bound.
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
# @!macro thread_pool_executor_method_prune_pool
|
|
75
|
+
# Prune the thread pool of unneeded threads
|
|
76
|
+
#
|
|
77
|
+
# What is being pruned is controlled by the min_threads and idletime
|
|
78
|
+
# parameters passed at pool creation time
|
|
79
|
+
#
|
|
80
|
+
# This is a no-op on some pool implementation (e.g. the Java one). The Ruby
|
|
81
|
+
# pool will auto-prune each time a new job is posted. You will need to call
|
|
82
|
+
# this method explicitely in case your application post jobs in bursts (a
|
|
83
|
+
# lot of jobs and then nothing for long periods)
|
|
77
84
|
|
|
78
85
|
# @!macro thread_pool_executor_public_api
|
|
79
86
|
#
|
|
@@ -111,6 +118,9 @@ module Concurrent
|
|
|
111
118
|
#
|
|
112
119
|
# @!method can_overflow?
|
|
113
120
|
# @!macro executor_service_method_can_overflow_question
|
|
121
|
+
#
|
|
122
|
+
# @!method prune_pool
|
|
123
|
+
# @!macro thread_pool_executor_method_prune_pool
|
|
114
124
|
|
|
115
125
|
|
|
116
126
|
|
|
@@ -20,7 +20,7 @@ if Concurrent.on_jruby?
|
|
|
20
20
|
|
|
21
21
|
def post(*args, &task)
|
|
22
22
|
raise ArgumentError.new('no block given') unless block_given?
|
|
23
|
-
return
|
|
23
|
+
return fallback_action(*args, &task).call unless running?
|
|
24
24
|
@executor.submit Job.new(args, task)
|
|
25
25
|
true
|
|
26
26
|
rescue Java::JavaUtilConcurrent::RejectedExecutionException
|
|
@@ -16,10 +16,16 @@ module Concurrent
|
|
|
16
16
|
|
|
17
17
|
def post(*args, &task)
|
|
18
18
|
raise ArgumentError.new('no block given') unless block_given?
|
|
19
|
-
synchronize
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
deferred_action = synchronize {
|
|
20
|
+
if running?
|
|
21
|
+
ns_execute(*args, &task)
|
|
22
|
+
else
|
|
23
|
+
fallback_action(*args, &task)
|
|
24
|
+
end
|
|
25
|
+
}
|
|
26
|
+
if deferred_action
|
|
27
|
+
deferred_action.call
|
|
28
|
+
else
|
|
23
29
|
true
|
|
24
30
|
end
|
|
25
31
|
end
|
|
@@ -93,13 +93,8 @@ module Concurrent
|
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
# @!visibility private
|
|
96
|
-
def ready_worker(worker)
|
|
97
|
-
synchronize { ns_ready_worker worker }
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# @!visibility private
|
|
101
|
-
def worker_not_old_enough(worker)
|
|
102
|
-
synchronize { ns_worker_not_old_enough worker }
|
|
96
|
+
def ready_worker(worker, last_message)
|
|
97
|
+
synchronize { ns_ready_worker worker, last_message }
|
|
103
98
|
end
|
|
104
99
|
|
|
105
100
|
# @!visibility private
|
|
@@ -112,6 +107,11 @@ module Concurrent
|
|
|
112
107
|
synchronize { @completed_task_count += 1 }
|
|
113
108
|
end
|
|
114
109
|
|
|
110
|
+
# @!macro thread_pool_executor_method_prune_pool
|
|
111
|
+
def prune_pool
|
|
112
|
+
synchronize { ns_prune_pool }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
115
|
private
|
|
116
116
|
|
|
117
117
|
# @!visibility private
|
|
@@ -156,10 +156,11 @@ module Concurrent
|
|
|
156
156
|
if ns_assign_worker(*args, &task) || ns_enqueue(*args, &task)
|
|
157
157
|
@scheduled_task_count += 1
|
|
158
158
|
else
|
|
159
|
-
|
|
159
|
+
return fallback_action(*args, &task)
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
ns_prune_pool if @next_gc_time < Concurrent.monotonic_time
|
|
163
|
+
nil
|
|
163
164
|
end
|
|
164
165
|
|
|
165
166
|
# @!visibility private
|
|
@@ -192,7 +193,7 @@ module Concurrent
|
|
|
192
193
|
# @!visibility private
|
|
193
194
|
def ns_assign_worker(*args, &task)
|
|
194
195
|
# keep growing if the pool is not at the minimum yet
|
|
195
|
-
worker = (@ready.pop if @pool.size >= @min_length) || ns_add_busy_worker
|
|
196
|
+
worker, _ = (@ready.pop if @pool.size >= @min_length) || ns_add_busy_worker
|
|
196
197
|
if worker
|
|
197
198
|
worker << [task, args]
|
|
198
199
|
true
|
|
@@ -223,7 +224,7 @@ module Concurrent
|
|
|
223
224
|
def ns_worker_died(worker)
|
|
224
225
|
ns_remove_busy_worker worker
|
|
225
226
|
replacement_worker = ns_add_busy_worker
|
|
226
|
-
ns_ready_worker replacement_worker, false if replacement_worker
|
|
227
|
+
ns_ready_worker replacement_worker, Concurrent.monotonic_time, false if replacement_worker
|
|
227
228
|
end
|
|
228
229
|
|
|
229
230
|
# creates new worker which has to receive work to do after it's added
|
|
@@ -242,29 +243,21 @@ module Concurrent
|
|
|
242
243
|
# handle ready worker, giving it new job or assigning back to @ready
|
|
243
244
|
#
|
|
244
245
|
# @!visibility private
|
|
245
|
-
def ns_ready_worker(worker, success = true)
|
|
246
|
+
def ns_ready_worker(worker, last_message, success = true)
|
|
246
247
|
task_and_args = @queue.shift
|
|
247
248
|
if task_and_args
|
|
248
249
|
worker << task_and_args
|
|
249
250
|
else
|
|
250
251
|
# stop workers when !running?, do not return them to @ready
|
|
251
252
|
if running?
|
|
252
|
-
|
|
253
|
+
raise unless last_message
|
|
254
|
+
@ready.push([worker, last_message])
|
|
253
255
|
else
|
|
254
256
|
worker.stop
|
|
255
257
|
end
|
|
256
258
|
end
|
|
257
259
|
end
|
|
258
260
|
|
|
259
|
-
# returns back worker to @ready which was not idle for enough time
|
|
260
|
-
#
|
|
261
|
-
# @!visibility private
|
|
262
|
-
def ns_worker_not_old_enough(worker)
|
|
263
|
-
# let's put workers coming from idle_test back to the start (as the oldest worker)
|
|
264
|
-
@ready.unshift(worker)
|
|
265
|
-
true
|
|
266
|
-
end
|
|
267
|
-
|
|
268
261
|
# removes a worker which is not in not tracked in @ready
|
|
269
262
|
#
|
|
270
263
|
# @!visibility private
|
|
@@ -278,10 +271,17 @@ module Concurrent
|
|
|
278
271
|
#
|
|
279
272
|
# @!visibility private
|
|
280
273
|
def ns_prune_pool
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
274
|
+
now = Concurrent.monotonic_time
|
|
275
|
+
stopped_workers = 0
|
|
276
|
+
while !@ready.empty? && (@pool.size - stopped_workers > @min_length)
|
|
277
|
+
worker, last_message = @ready.first
|
|
278
|
+
if now - last_message > self.idletime
|
|
279
|
+
stopped_workers += 1
|
|
280
|
+
@ready.shift
|
|
281
|
+
worker << :stop
|
|
282
|
+
else break
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
285
|
|
|
286
286
|
@next_gc_time = Concurrent.monotonic_time + @gc_interval
|
|
287
287
|
end
|
|
@@ -330,19 +330,10 @@ module Concurrent
|
|
|
330
330
|
|
|
331
331
|
def create_worker(queue, pool, idletime)
|
|
332
332
|
Thread.new(queue, pool, idletime) do |my_queue, my_pool, my_idletime|
|
|
333
|
-
last_message = Concurrent.monotonic_time
|
|
334
333
|
catch(:stop) do
|
|
335
334
|
loop do
|
|
336
335
|
|
|
337
336
|
case message = my_queue.pop
|
|
338
|
-
when :idle_test
|
|
339
|
-
if (Concurrent.monotonic_time - last_message) > my_idletime
|
|
340
|
-
my_pool.remove_busy_worker(self)
|
|
341
|
-
throw :stop
|
|
342
|
-
else
|
|
343
|
-
my_pool.worker_not_old_enough(self)
|
|
344
|
-
end
|
|
345
|
-
|
|
346
337
|
when :stop
|
|
347
338
|
my_pool.remove_busy_worker(self)
|
|
348
339
|
throw :stop
|
|
@@ -350,9 +341,7 @@ module Concurrent
|
|
|
350
341
|
else
|
|
351
342
|
task, args = message
|
|
352
343
|
run_task my_pool, task, args
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
my_pool.ready_worker(self)
|
|
344
|
+
my_pool.ready_worker(self, Concurrent.monotonic_time)
|
|
356
345
|
end
|
|
357
346
|
end
|
|
358
347
|
end
|
|
@@ -16,10 +16,10 @@ module Concurrent
|
|
|
16
16
|
|
|
17
17
|
# @return [Array]
|
|
18
18
|
def execute(*args)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
value = reason = nil
|
|
19
|
+
success = true
|
|
20
|
+
value = reason = nil
|
|
22
21
|
|
|
22
|
+
synchronize do
|
|
23
23
|
begin
|
|
24
24
|
value = @task.call(*args)
|
|
25
25
|
success = true
|
|
@@ -27,9 +27,9 @@ module Concurrent
|
|
|
27
27
|
reason = ex
|
|
28
28
|
success = false
|
|
29
29
|
end
|
|
30
|
-
|
|
31
|
-
[success, value, reason]
|
|
32
30
|
end
|
|
31
|
+
|
|
32
|
+
[success, value, reason]
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -15,7 +15,10 @@ module Concurrent
|
|
|
15
15
|
when Concurrent.on_cruby?
|
|
16
16
|
require 'concurrent/collection/map/mri_map_backend'
|
|
17
17
|
MriMapBackend
|
|
18
|
-
when Concurrent.
|
|
18
|
+
when Concurrent.on_truffleruby? && defined?(::TruffleRuby::ConcurrentMap)
|
|
19
|
+
require 'concurrent/collection/map/truffleruby_map_backend'
|
|
20
|
+
TruffleRubyMapBackend
|
|
21
|
+
when Concurrent.on_truffleruby? || Concurrent.on_rbx?
|
|
19
22
|
require 'concurrent/collection/map/atomic_reference_map_backend'
|
|
20
23
|
AtomicReferenceMapBackend
|
|
21
24
|
else
|
|
@@ -114,7 +117,7 @@ module Concurrent
|
|
|
114
117
|
# @return [true, false] true if deleted
|
|
115
118
|
# @!macro map.atomic_method
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
#
|
|
118
121
|
def initialize(options = nil, &block)
|
|
119
122
|
if options.kind_of?(::Hash)
|
|
120
123
|
validate_options_hash!(options)
|
|
@@ -143,8 +146,15 @@ module Concurrent
|
|
|
143
146
|
end
|
|
144
147
|
end
|
|
145
148
|
|
|
149
|
+
# Set a value with key
|
|
150
|
+
# @param [Object] key
|
|
151
|
+
# @param [Object] value
|
|
152
|
+
# @return [Object] the new value
|
|
153
|
+
def []=(key, value)
|
|
154
|
+
super
|
|
155
|
+
end
|
|
156
|
+
|
|
146
157
|
alias_method :get, :[]
|
|
147
|
-
# TODO (pitr-ch 30-Oct-2018): doc
|
|
148
158
|
alias_method :put, :[]=
|
|
149
159
|
|
|
150
160
|
# Get a value with key, or default_value when key is absent,
|
|
@@ -271,7 +281,6 @@ module Concurrent
|
|
|
271
281
|
each_pair { |k, v| return k if v == value }
|
|
272
282
|
nil
|
|
273
283
|
end unless method_defined?(:key)
|
|
274
|
-
alias_method :index, :key if RUBY_VERSION < '1.9'
|
|
275
284
|
|
|
276
285
|
# Is map empty?
|
|
277
286
|
# @return [true, false]
|
|
@@ -58,29 +58,42 @@ module Concurrent
|
|
|
58
58
|
# @example Basic usage
|
|
59
59
|
#
|
|
60
60
|
# require 'concurrent'
|
|
61
|
-
# require '
|
|
62
|
-
# require 'open-uri'
|
|
61
|
+
# require 'csv'
|
|
62
|
+
# require 'open-uri'
|
|
63
63
|
#
|
|
64
64
|
# class Ticker
|
|
65
|
-
# def get_year_end_closing(symbol, year)
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
65
|
+
# def get_year_end_closing(symbol, year, api_key)
|
|
66
|
+
# uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv"
|
|
67
|
+
# data = []
|
|
68
|
+
# csv = URI.parse(uri).read
|
|
69
|
+
# if csv.include?('call frequency')
|
|
70
|
+
# return :rate_limit_exceeded
|
|
71
|
+
# end
|
|
72
|
+
# CSV.parse(csv, headers: true) do |row|
|
|
73
|
+
# data << row['close'].to_f if row['timestamp'].include?(year.to_s)
|
|
74
|
+
# end
|
|
75
|
+
# year_end = data.first
|
|
76
|
+
# year_end
|
|
77
|
+
# rescue => e
|
|
78
|
+
# p e
|
|
79
|
+
# end
|
|
70
80
|
# end
|
|
71
81
|
#
|
|
82
|
+
# api_key = ENV['ALPHAVANTAGE_KEY']
|
|
83
|
+
# abort(error_message) unless api_key
|
|
84
|
+
#
|
|
72
85
|
# # Future
|
|
73
|
-
# price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013) }
|
|
86
|
+
# price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013, api_key) }
|
|
74
87
|
# price.state #=> :pending
|
|
75
|
-
#
|
|
76
|
-
# price.value #=>
|
|
77
|
-
# price.state #=> :fulfilled
|
|
88
|
+
# price.pending? #=> true
|
|
89
|
+
# price.value(0) #=> nil (does not block)
|
|
78
90
|
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
91
|
+
# sleep(1) # do other stuff
|
|
92
|
+
#
|
|
93
|
+
# price.value #=> 63.65 (after blocking if necessary)
|
|
94
|
+
# price.state #=> :fulfilled
|
|
95
|
+
# price.fulfilled? #=> true
|
|
96
|
+
# price.value #=> 63.65
|
|
84
97
|
#
|
|
85
98
|
# @example Successful task execution
|
|
86
99
|
#
|
|
@@ -19,13 +19,19 @@ module Concurrent
|
|
|
19
19
|
#
|
|
20
20
|
# @see http://ruby-doc.org/stdlib-2.4.0/libdoc/set/rdoc/Set.html Ruby standard library `Set`
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
# @!macro internal_implementation_note
|
|
24
23
|
SetImplementation = case
|
|
25
24
|
when Concurrent.on_cruby?
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
|
|
25
|
+
# The CRuby implementation of Set is written in Ruby itself and is
|
|
26
|
+
# not thread safe for certain methods.
|
|
27
|
+
require 'monitor'
|
|
28
|
+
require 'concurrent/thread_safe/util/data_structures'
|
|
29
|
+
|
|
30
|
+
class CRubySet < ::Set
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
ThreadSafe::Util.make_synchronized_on_cruby CRubySet
|
|
34
|
+
CRubySet
|
|
29
35
|
|
|
30
36
|
when Concurrent.on_jruby?
|
|
31
37
|
require 'jruby/synchronized'
|
|
@@ -33,6 +39,7 @@ module Concurrent
|
|
|
33
39
|
class JRubySet < ::Set
|
|
34
40
|
include JRuby::Synchronized
|
|
35
41
|
end
|
|
42
|
+
|
|
36
43
|
JRubySet
|
|
37
44
|
|
|
38
45
|
when Concurrent.on_rbx?
|
|
@@ -41,7 +48,8 @@ module Concurrent
|
|
|
41
48
|
|
|
42
49
|
class RbxSet < ::Set
|
|
43
50
|
end
|
|
44
|
-
|
|
51
|
+
|
|
52
|
+
ThreadSafe::Util.make_synchronized_on_rbx RbxSet
|
|
45
53
|
RbxSet
|
|
46
54
|
|
|
47
55
|
when Concurrent.on_truffleruby?
|
|
@@ -50,7 +58,7 @@ module Concurrent
|
|
|
50
58
|
class TruffleRubySet < ::Set
|
|
51
59
|
end
|
|
52
60
|
|
|
53
|
-
ThreadSafe::Util.make_synchronized_on_truffleruby
|
|
61
|
+
ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubySet
|
|
54
62
|
TruffleRubySet
|
|
55
63
|
|
|
56
64
|
else
|
|
@@ -4,9 +4,7 @@ module Concurrent
|
|
|
4
4
|
# @!visibility private
|
|
5
5
|
# @!macro internal_implementation_note
|
|
6
6
|
LockableObjectImplementation = case
|
|
7
|
-
when Concurrent.on_cruby?
|
|
8
|
-
MonitorLockableObject
|
|
9
|
-
when Concurrent.on_cruby? && Concurrent.ruby_version(:>, 1, 9, 3)
|
|
7
|
+
when Concurrent.on_cruby?
|
|
10
8
|
MutexLockableObject
|
|
11
9
|
when Concurrent.on_jruby?
|
|
12
10
|
JRubyLockableObject
|
|
@@ -32,6 +32,12 @@ module Concurrent
|
|
|
32
32
|
@__Condition__ = ::ConditionVariable.new
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def initialize_copy(other)
|
|
36
|
+
super
|
|
37
|
+
@__Lock__ = ::Mutex.new
|
|
38
|
+
@__Condition__ = ::ConditionVariable.new
|
|
39
|
+
end
|
|
40
|
+
|
|
35
41
|
protected
|
|
36
42
|
|
|
37
43
|
def synchronize
|
|
@@ -61,6 +67,12 @@ module Concurrent
|
|
|
61
67
|
@__Condition__ = @__Lock__.new_cond
|
|
62
68
|
end
|
|
63
69
|
|
|
70
|
+
def initialize_copy(other)
|
|
71
|
+
super
|
|
72
|
+
@__Lock__ = ::Monitor.new
|
|
73
|
+
@__Condition__ = @__Lock__.new_cond
|
|
74
|
+
end
|
|
75
|
+
|
|
64
76
|
protected
|
|
65
77
|
|
|
66
78
|
def synchronize # TODO may be a problem with lock.synchronize { lock.wait }
|
|
@@ -12,12 +12,37 @@ end
|
|
|
12
12
|
module Concurrent
|
|
13
13
|
module ThreadSafe
|
|
14
14
|
module Util
|
|
15
|
+
def self.make_synchronized_on_cruby(klass)
|
|
16
|
+
klass.class_eval do
|
|
17
|
+
def initialize(*args, &block)
|
|
18
|
+
@_monitor = Monitor.new
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize_copy(other)
|
|
23
|
+
# make sure a copy is not sharing a monitor with the original object!
|
|
24
|
+
@_monitor = Monitor.new
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
klass.superclass.instance_methods(false).each do |method|
|
|
30
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
31
|
+
def #{method}(*args)
|
|
32
|
+
monitor = @_monitor
|
|
33
|
+
monitor or raise("BUG: Internal monitor was not properly initialized. Please report this to the concurrent-ruby developers.")
|
|
34
|
+
monitor.synchronize { super }
|
|
35
|
+
end
|
|
36
|
+
RUBY
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
15
40
|
def self.make_synchronized_on_rbx(klass)
|
|
16
41
|
klass.class_eval do
|
|
17
42
|
private
|
|
18
43
|
|
|
19
44
|
def _mon_initialize
|
|
20
|
-
@_monitor
|
|
45
|
+
@_monitor ||= Monitor.new # avoid double initialisation
|
|
21
46
|
end
|
|
22
47
|
|
|
23
48
|
def self.new(*args)
|
|
@@ -25,9 +25,7 @@ module Concurrent
|
|
|
25
25
|
# Should the task experience an unrecoverable crash only the task thread will
|
|
26
26
|
# crash. This makes the `TimerTask` very fault tolerant. Additionally, the
|
|
27
27
|
# `TimerTask` thread can respond to the success or failure of the task,
|
|
28
|
-
# performing logging or ancillary operations.
|
|
29
|
-
# configured with a timeout value allowing it to kill a task that runs too
|
|
30
|
-
# long.
|
|
28
|
+
# performing logging or ancillary operations.
|
|
31
29
|
#
|
|
32
30
|
# One other advantage of `TimerTask` is that it forces the business logic to
|
|
33
31
|
# be completely decoupled from the concurrency logic. The business logic can
|
|
@@ -48,9 +46,7 @@ module Concurrent
|
|
|
48
46
|
# {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
|
|
49
47
|
# Observable} module. On execution the `TimerTask` will notify the observers
|
|
50
48
|
# with three arguments: time of execution, the result of the block (or nil on
|
|
51
|
-
# failure), and any raised exceptions (or nil on success).
|
|
52
|
-
# interval is exceeded the observer will receive a `Concurrent::TimeoutError`
|
|
53
|
-
# object as the third argument.
|
|
49
|
+
# failure), and any raised exceptions (or nil on success).
|
|
54
50
|
#
|
|
55
51
|
# @!macro copy_options
|
|
56
52
|
#
|
|
@@ -59,20 +55,18 @@ module Concurrent
|
|
|
59
55
|
# task.execute
|
|
60
56
|
#
|
|
61
57
|
# task.execution_interval #=> 60 (default)
|
|
62
|
-
# task.timeout_interval #=> 30 (default)
|
|
63
58
|
#
|
|
64
59
|
# # wait 60 seconds...
|
|
65
60
|
# #=> 'Boom!'
|
|
66
61
|
#
|
|
67
62
|
# task.shutdown #=> true
|
|
68
63
|
#
|
|
69
|
-
# @example Configuring `:execution_interval`
|
|
70
|
-
# task = Concurrent::TimerTask.new(execution_interval: 5
|
|
64
|
+
# @example Configuring `:execution_interval`
|
|
65
|
+
# task = Concurrent::TimerTask.new(execution_interval: 5) do
|
|
71
66
|
# puts 'Boom!'
|
|
72
67
|
# end
|
|
73
68
|
#
|
|
74
69
|
# task.execution_interval #=> 5
|
|
75
|
-
# task.timeout_interval #=> 5
|
|
76
70
|
#
|
|
77
71
|
# @example Immediate execution with `:run_now`
|
|
78
72
|
# task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' }
|
|
@@ -115,15 +109,13 @@ module Concurrent
|
|
|
115
109
|
# def update(time, result, ex)
|
|
116
110
|
# if result
|
|
117
111
|
# print "(#{time}) Execution successfully returned #{result}\n"
|
|
118
|
-
# elsif ex.is_a?(Concurrent::TimeoutError)
|
|
119
|
-
# print "(#{time}) Execution timed out\n"
|
|
120
112
|
# else
|
|
121
113
|
# print "(#{time}) Execution failed with error #{ex}\n"
|
|
122
114
|
# end
|
|
123
115
|
# end
|
|
124
116
|
# end
|
|
125
117
|
#
|
|
126
|
-
# task = Concurrent::TimerTask.new(execution_interval: 1
|
|
118
|
+
# task = Concurrent::TimerTask.new(execution_interval: 1){ 42 }
|
|
127
119
|
# task.add_observer(TaskObserver.new)
|
|
128
120
|
# task.execute
|
|
129
121
|
# sleep 4
|
|
@@ -133,7 +125,7 @@ module Concurrent
|
|
|
133
125
|
# #=> (2013-10-13 19:09:00 -0400) Execution successfully returned 42
|
|
134
126
|
# task.shutdown
|
|
135
127
|
#
|
|
136
|
-
# task = Concurrent::TimerTask.new(execution_interval: 1
|
|
128
|
+
# task = Concurrent::TimerTask.new(execution_interval: 1){ sleep }
|
|
137
129
|
# task.add_observer(TaskObserver.new)
|
|
138
130
|
# task.execute
|
|
139
131
|
#
|
|
@@ -169,8 +161,6 @@ module Concurrent
|
|
|
169
161
|
# @param [Hash] opts the options defining task execution.
|
|
170
162
|
# @option opts [Integer] :execution_interval number of seconds between
|
|
171
163
|
# task executions (default: EXECUTION_INTERVAL)
|
|
172
|
-
# @option opts [Integer] :timeout_interval number of seconds a task can
|
|
173
|
-
# run before it is considered to have failed (default: TIMEOUT_INTERVAL)
|
|
174
164
|
# @option opts [Boolean] :run_now Whether to run the task immediately
|
|
175
165
|
# upon instantiation or to wait until the first # execution_interval
|
|
176
166
|
# has passed (default: false)
|
|
@@ -256,18 +246,14 @@ module Concurrent
|
|
|
256
246
|
# @return [Fixnum] Number of seconds the task can run before it is
|
|
257
247
|
# considered to have failed.
|
|
258
248
|
def timeout_interval
|
|
259
|
-
|
|
249
|
+
warn 'TimerTask timeouts are now ignored as these were not able to be implemented correctly'
|
|
260
250
|
end
|
|
261
251
|
|
|
262
252
|
# @!attribute [rw] timeout_interval
|
|
263
253
|
# @return [Fixnum] Number of seconds the task can run before it is
|
|
264
254
|
# considered to have failed.
|
|
265
255
|
def timeout_interval=(value)
|
|
266
|
-
|
|
267
|
-
raise ArgumentError.new('must be greater than zero')
|
|
268
|
-
else
|
|
269
|
-
synchronize { @timeout_interval = value }
|
|
270
|
-
end
|
|
256
|
+
warn 'TimerTask timeouts are now ignored as these were not able to be implemented correctly'
|
|
271
257
|
end
|
|
272
258
|
|
|
273
259
|
private :post, :<<
|
|
@@ -278,7 +264,9 @@ module Concurrent
|
|
|
278
264
|
set_deref_options(opts)
|
|
279
265
|
|
|
280
266
|
self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
|
|
281
|
-
|
|
267
|
+
if opts[:timeout] || opts[:timeout_interval]
|
|
268
|
+
warn 'TimeTask timeouts are now ignored as these were not able to be implemented correctly'
|
|
269
|
+
end
|
|
282
270
|
@run_now = opts[:now] || opts[:run_now]
|
|
283
271
|
@executor = Concurrent::SafeTaskExecutor.new(task)
|
|
284
272
|
@running = Concurrent::AtomicBoolean.new(false)
|
|
@@ -308,7 +296,6 @@ module Concurrent
|
|
|
308
296
|
# @!visibility private
|
|
309
297
|
def execute_task(completion)
|
|
310
298
|
return nil unless @running.true?
|
|
311
|
-
ScheduledTask.execute(timeout_interval, args: [completion], &method(:timeout_task))
|
|
312
299
|
_success, value, reason = @executor.execute(self)
|
|
313
300
|
if completion.try?
|
|
314
301
|
self.value = value
|
|
@@ -320,14 +307,5 @@ module Concurrent
|
|
|
320
307
|
end
|
|
321
308
|
nil
|
|
322
309
|
end
|
|
323
|
-
|
|
324
|
-
# @!visibility private
|
|
325
|
-
def timeout_task(completion)
|
|
326
|
-
return unless @running.true?
|
|
327
|
-
if completion.try?
|
|
328
|
-
schedule_next_task
|
|
329
|
-
observers.notify_observers(Time.now, nil, Concurrent::TimeoutError.new)
|
|
330
|
-
end
|
|
331
|
-
end
|
|
332
310
|
end
|
|
333
311
|
end
|