concurrent-ruby 0.2.2 → 0.3.0.pre.1
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/README.md +45 -42
- data/lib/concurrent.rb +5 -6
- data/lib/concurrent/agent.rb +29 -33
- data/lib/concurrent/cached_thread_pool.rb +26 -105
- data/lib/concurrent/channel.rb +94 -0
- data/lib/concurrent/event.rb +8 -17
- data/lib/concurrent/executor.rb +68 -72
- data/lib/concurrent/fixed_thread_pool.rb +15 -83
- data/lib/concurrent/functions.rb +7 -22
- data/lib/concurrent/future.rb +29 -9
- data/lib/concurrent/null_thread_pool.rb +5 -2
- data/lib/concurrent/obligation.rb +6 -16
- data/lib/concurrent/promise.rb +9 -10
- data/lib/concurrent/runnable.rb +103 -0
- data/lib/concurrent/supervisor.rb +271 -44
- data/lib/concurrent/thread_pool.rb +112 -39
- data/lib/concurrent/version.rb +1 -1
- data/md/executor.md +9 -3
- data/md/goroutine.md +11 -9
- data/md/reactor.md +32 -0
- data/md/supervisor.md +43 -0
- data/spec/concurrent/agent_spec.rb +128 -51
- data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
- data/spec/concurrent/channel_spec.rb +446 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
- data/spec/concurrent/event_spec.rb +0 -19
- data/spec/concurrent/executor_spec.rb +167 -119
- data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
- data/spec/concurrent/functions_spec.rb +0 -20
- data/spec/concurrent/future_spec.rb +88 -0
- data/spec/concurrent/null_thread_pool_spec.rb +23 -2
- data/spec/concurrent/obligation_shared.rb +0 -5
- data/spec/concurrent/promise_spec.rb +9 -10
- data/spec/concurrent/runnable_shared.rb +62 -0
- data/spec/concurrent/runnable_spec.rb +233 -0
- data/spec/concurrent/supervisor_spec.rb +912 -47
- data/spec/concurrent/thread_pool_shared.rb +18 -31
- data/spec/spec_helper.rb +10 -3
- metadata +17 -23
- data/lib/concurrent/defer.rb +0 -65
- data/lib/concurrent/reactor.rb +0 -166
- data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
- data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
- data/lib/concurrent/utilities.rb +0 -32
- data/md/defer.md +0 -174
- data/spec/concurrent/defer_spec.rb +0 -199
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
- data/spec/concurrent/reactor_spec.rb +0 -364
- data/spec/concurrent/utilities_spec.rb +0 -74
data/lib/concurrent/event.rb
CHANGED
@@ -16,39 +16,30 @@ module Concurrent
|
|
16
16
|
return @set == true
|
17
17
|
end
|
18
18
|
|
19
|
-
def set
|
19
|
+
def set
|
20
20
|
return true if set?
|
21
|
-
@mutex.synchronize
|
21
|
+
@mutex.synchronize do
|
22
22
|
@set = true
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
@set = ! pulse
|
28
|
-
}
|
23
|
+
@waiting.times { @notifier << :set }
|
24
|
+
@waiting = 0
|
25
|
+
end
|
29
26
|
return true
|
30
27
|
end
|
31
28
|
|
32
29
|
def reset
|
33
|
-
@mutex.synchronize {
|
34
|
-
@set = false
|
35
|
-
}
|
30
|
+
@mutex.synchronize { @set = false }
|
36
31
|
return true
|
37
32
|
end
|
38
33
|
|
39
|
-
def pulse
|
40
|
-
return set(true)
|
41
|
-
end
|
42
|
-
|
43
34
|
def wait(timeout = nil)
|
44
35
|
return true if set?
|
45
36
|
|
37
|
+
@mutex.synchronize { @waiting += 1 }
|
38
|
+
|
46
39
|
if timeout.nil?
|
47
|
-
@waiting += 1
|
48
40
|
@notifier.pop
|
49
41
|
else
|
50
42
|
Timeout::timeout(timeout) do
|
51
|
-
@waiting += 1
|
52
43
|
@notifier.pop
|
53
44
|
end
|
54
45
|
end
|
data/lib/concurrent/executor.rb
CHANGED
@@ -1,96 +1,92 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'concurrent/runnable'
|
2
3
|
|
3
4
|
module Concurrent
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
class Executor
|
7
|
+
include Runnable
|
8
|
+
behavior(:runnable)
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
attr_reader :execution_interval
|
11
|
-
attr_reader :timeout_interval
|
10
|
+
EXECUTION_INTERVAL = 60
|
11
|
+
TIMEOUT_INTERVAL = 30
|
12
12
|
|
13
|
-
|
13
|
+
STDOUT_LOGGER = proc do |name, level, msg|
|
14
|
+
print "%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg]
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@timeout_interval = timeout_interval
|
19
|
-
@thread = thread
|
20
|
-
@thread[:stop] = false
|
21
|
-
end
|
17
|
+
attr_reader :name
|
18
|
+
attr_reader :execution_interval
|
19
|
+
attr_reader :timeout_interval
|
22
20
|
|
23
|
-
|
21
|
+
def initialize(name, opts = {}, &block)
|
22
|
+
raise ArgumentError.new('no block given') unless block_given?
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
@name = name
|
25
|
+
@execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
|
26
|
+
@timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
|
27
|
+
@run_now = opts[:now] || opts[:run_now] || false
|
28
|
+
@logger = opts[:logger] || STDOUT_LOGGER
|
29
|
+
@block_args = opts[:args] || opts [:arguments] || []
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
return nil
|
32
|
-
elsif limit.nil?
|
33
|
-
return @thread.join
|
34
|
-
else
|
35
|
-
return @thread.join(limit)
|
36
|
-
end
|
37
|
-
end
|
31
|
+
@task = block
|
32
|
+
end
|
38
33
|
|
39
|
-
|
40
|
-
|
34
|
+
def kill
|
35
|
+
return true unless running?
|
36
|
+
mutex.synchronize do
|
37
|
+
@running = false
|
38
|
+
Thread.kill(@worker) unless @worker.nil?
|
39
|
+
Thread.kill(@monitor) unless @monitor.nil?
|
41
40
|
end
|
41
|
+
return true
|
42
|
+
rescue
|
43
|
+
return false
|
44
|
+
ensure
|
45
|
+
@worker = @monitor = nil
|
46
|
+
end
|
47
|
+
alias_method :terminate, :kill
|
42
48
|
|
43
|
-
|
44
|
-
|
45
|
-
stop
|
46
|
-
Thread.kill(@thread)
|
47
|
-
@thread = nil
|
48
|
-
end
|
49
|
-
end
|
50
|
-
alias_method :terminate, :kill
|
49
|
+
def status
|
50
|
+
return @monitor.status unless @monitor.nil?
|
51
51
|
end
|
52
52
|
|
53
|
-
|
54
|
-
TIMEOUT_INTERVAL = 30
|
53
|
+
protected
|
55
54
|
|
56
|
-
|
57
|
-
|
55
|
+
def on_run
|
56
|
+
@monitor = Thread.current
|
58
57
|
end
|
59
58
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
|
65
|
-
run_now = opts[:now] || opts[:run_now] || false
|
66
|
-
logger = opts[:logger] || STDOUT_LOGGER
|
67
|
-
block_args = opts[:args] || opts [:arguments] || []
|
59
|
+
def on_stop
|
60
|
+
@monitor.wakeup if @monitor.alive?
|
61
|
+
Thread.pass
|
62
|
+
end
|
68
63
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
worker = Thread.new{ yield(*args) }
|
75
|
-
worker.abort_on_exception = false
|
76
|
-
if worker.join(timeout_interval).nil?
|
77
|
-
logger.call(name, :warn, "execution timed out after #{timeout_interval} seconds")
|
78
|
-
else
|
79
|
-
logger.call(name, :info, 'execution completed successfully')
|
80
|
-
end
|
81
|
-
rescue Exception => ex
|
82
|
-
logger.call(name, :error, "execution failed with error '#{ex}'")
|
83
|
-
ensure
|
84
|
-
Thread.kill(worker)
|
85
|
-
worker = nil
|
86
|
-
end
|
87
|
-
break if Thread.current[:stop]
|
88
|
-
sleep(execution_interval)
|
89
|
-
end
|
64
|
+
def on_task
|
65
|
+
if @run_now
|
66
|
+
@run_now = false
|
67
|
+
else
|
68
|
+
sleep(@execution_interval)
|
90
69
|
end
|
70
|
+
execute_task
|
71
|
+
end
|
91
72
|
|
92
|
-
|
93
|
-
|
73
|
+
def execute_task
|
74
|
+
@worker = Thread.new do
|
75
|
+
Thread.current.abort_on_exception = false
|
76
|
+
@task.call(*@block_args)
|
77
|
+
end
|
78
|
+
if @worker.join(@timeout_interval).nil?
|
79
|
+
@logger.call(@name, :warn, "execution timed out after #{@timeout_interval} seconds")
|
80
|
+
else
|
81
|
+
@logger.call(@name, :info, 'execution completed successfully')
|
82
|
+
end
|
83
|
+
rescue Exception => ex
|
84
|
+
@logger.call(@name, :error, "execution failed with error '#{ex}'")
|
85
|
+
ensure
|
86
|
+
unless @worker.nil?
|
87
|
+
Thread.kill(@worker)
|
88
|
+
@worker = nil
|
89
|
+
end
|
94
90
|
end
|
95
91
|
end
|
96
92
|
end
|
@@ -1,99 +1,31 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
1
|
require 'concurrent/thread_pool'
|
4
|
-
require 'concurrent/event'
|
5
2
|
|
6
3
|
module Concurrent
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
|
12
|
-
class FixedThreadPool < ThreadPool
|
13
|
-
behavior(:thread_pool)
|
14
|
-
|
15
|
-
MIN_POOL_SIZE = 1
|
16
|
-
MAX_POOL_SIZE = 1024
|
17
|
-
|
18
|
-
def initialize(size)
|
19
|
-
super()
|
20
|
-
if size < MIN_POOL_SIZE || size > MAX_POOL_SIZE
|
21
|
-
raise ArgumentError.new("size must be between #{MIN_POOL_SIZE} and #{MAX_POOL_SIZE}")
|
22
|
-
end
|
23
|
-
|
24
|
-
@pool = size.times.collect{ create_worker_thread }
|
25
|
-
collect_garbage
|
26
|
-
end
|
27
|
-
|
28
|
-
def kill
|
29
|
-
mutex.synchronize do
|
30
|
-
@status = :killed
|
31
|
-
@pool.each{|t| Thread.kill(t) }
|
32
|
-
end
|
33
|
-
end
|
5
|
+
class FixedThreadPool < AbstractThreadPool
|
6
|
+
behavior(:global_thread_pool)
|
34
7
|
|
35
|
-
def size
|
36
|
-
|
37
|
-
return @pool.length
|
38
|
-
else
|
39
|
-
return 0
|
40
|
-
end
|
8
|
+
def initialize(size, opts = {})
|
9
|
+
super(opts.merge(max_threads: size))
|
41
10
|
end
|
42
11
|
|
43
12
|
def post(*args, &block)
|
44
13
|
raise ArgumentError.new('no block given') unless block_given?
|
45
|
-
|
46
|
-
@
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# @private
|
54
|
-
def status # :nodoc:
|
55
|
-
mutex.synchronize do
|
56
|
-
@pool.collect{|t| t.status }
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
# @private
|
63
|
-
def create_worker_thread # :nodoc:
|
64
|
-
thread = Thread.new do
|
65
|
-
loop do
|
66
|
-
task = @queue.pop
|
67
|
-
if task == :stop
|
68
|
-
break
|
69
|
-
else
|
70
|
-
task.last.call(*task.first)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
@pool.delete(Thread.current)
|
74
|
-
if @pool.empty?
|
75
|
-
@termination.set
|
76
|
-
@status = :shutdown unless killed?
|
14
|
+
return @mutex.synchronize do
|
15
|
+
if @state == :running
|
16
|
+
@queue << [args, block]
|
17
|
+
@pool << create_worker_thread if @pool.size < @max_threads
|
18
|
+
true
|
19
|
+
else
|
20
|
+
false
|
77
21
|
end
|
78
22
|
end
|
79
|
-
|
80
|
-
thread.abort_on_exception = false
|
81
|
-
return thread
|
82
23
|
end
|
83
24
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
mutex.synchronize do
|
89
|
-
@pool.size.times do |i|
|
90
|
-
if @pool[i].status.nil?
|
91
|
-
@pool[i] = create_worker_thread
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
@collector.abort_on_exception = false
|
25
|
+
protected
|
26
|
+
|
27
|
+
def collect_garbage
|
28
|
+
@pool.reject! {|context| ! context.status }
|
97
29
|
end
|
98
30
|
end
|
99
31
|
end
|
data/lib/concurrent/functions.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'concurrent/agent'
|
2
|
-
require 'concurrent/defer'
|
3
2
|
require 'concurrent/future'
|
4
3
|
require 'concurrent/promise'
|
5
4
|
|
@@ -7,34 +6,20 @@ module Kernel
|
|
7
6
|
|
8
7
|
## agent
|
9
8
|
|
10
|
-
def agent(
|
11
|
-
return Concurrent::Agent.new(
|
9
|
+
def agent(*args, &block)
|
10
|
+
return Concurrent::Agent.new(*args, &block)
|
12
11
|
end
|
13
12
|
module_function :agent
|
14
13
|
|
15
|
-
def post(object, &block)
|
14
|
+
def post(object, *args, &block)
|
16
15
|
if object.respond_to?(:post)
|
17
|
-
return object.post(&block)
|
16
|
+
return object.post(*args, &block)
|
18
17
|
else
|
19
18
|
raise ArgumentError.new('object does not support #post')
|
20
19
|
end
|
21
20
|
end
|
22
21
|
module_function :post
|
23
22
|
|
24
|
-
## defer
|
25
|
-
|
26
|
-
def defer(*args, &block)
|
27
|
-
return Concurrent::Defer.new(*args, &block)
|
28
|
-
end
|
29
|
-
module_function :defer
|
30
|
-
|
31
|
-
## executor
|
32
|
-
|
33
|
-
def executor(*args, &block)
|
34
|
-
return Concurrent::Executor.run(*args, &block)
|
35
|
-
end
|
36
|
-
module_function :executor
|
37
|
-
|
38
23
|
## future
|
39
24
|
|
40
25
|
def future(*args, &block)
|
@@ -44,11 +29,11 @@ module Kernel
|
|
44
29
|
|
45
30
|
## obligation
|
46
31
|
|
47
|
-
def deref(object,
|
32
|
+
def deref(object, *args, &block)
|
48
33
|
if object.respond_to?(:deref)
|
49
|
-
return object.deref(
|
34
|
+
return object.deref(*args, &block)
|
50
35
|
elsif object.respond_to?(:value)
|
51
|
-
return object.value(
|
36
|
+
return object.value(*args, &block)
|
52
37
|
else
|
53
38
|
raise ArgumentError.new('object does not support #deref')
|
54
39
|
end
|
data/lib/concurrent/future.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'observer'
|
2
3
|
|
3
4
|
require 'concurrent/global_thread_pool'
|
4
5
|
require 'concurrent/obligation'
|
5
|
-
require 'concurrent/utilities'
|
6
6
|
|
7
7
|
module Concurrent
|
8
8
|
|
9
9
|
class Future
|
10
10
|
include Obligation
|
11
|
+
include Observable
|
11
12
|
include UsesGlobalThreadPool
|
12
13
|
|
13
14
|
behavior(:future)
|
14
15
|
|
15
16
|
def initialize(*args, &block)
|
17
|
+
@mutex = Mutex.new
|
16
18
|
unless block_given?
|
17
19
|
@state = :fulfilled
|
18
20
|
else
|
@@ -24,18 +26,36 @@ module Concurrent
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
29
|
+
def add_observer(observer, func = :update)
|
30
|
+
@mutex.synchronize do
|
31
|
+
if event.set?
|
32
|
+
Future.thread_pool.post(func, Time.now, @value, @reason) do |f, *args|
|
33
|
+
observer.send(f, *args)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
return func
|
40
|
+
end
|
41
|
+
|
27
42
|
private
|
28
43
|
|
29
44
|
# @private
|
30
45
|
def work(*args) # :nodoc:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
46
|
+
begin
|
47
|
+
@value = yield(*args)
|
48
|
+
@state = :fulfilled
|
49
|
+
rescue Exception => ex
|
50
|
+
@reason = ex
|
51
|
+
@state = :rejected
|
52
|
+
ensure
|
53
|
+
@mutex.synchronize {
|
54
|
+
event.set
|
55
|
+
changed
|
56
|
+
notify_observers(Time.now, @value, @reason)
|
57
|
+
delete_observers
|
58
|
+
}
|
39
59
|
end
|
40
60
|
end
|
41
61
|
end
|