concurrent-ruby 0.2.2 → 0.3.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|