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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -42
  3. data/lib/concurrent.rb +5 -6
  4. data/lib/concurrent/agent.rb +29 -33
  5. data/lib/concurrent/cached_thread_pool.rb +26 -105
  6. data/lib/concurrent/channel.rb +94 -0
  7. data/lib/concurrent/event.rb +8 -17
  8. data/lib/concurrent/executor.rb +68 -72
  9. data/lib/concurrent/fixed_thread_pool.rb +15 -83
  10. data/lib/concurrent/functions.rb +7 -22
  11. data/lib/concurrent/future.rb +29 -9
  12. data/lib/concurrent/null_thread_pool.rb +5 -2
  13. data/lib/concurrent/obligation.rb +6 -16
  14. data/lib/concurrent/promise.rb +9 -10
  15. data/lib/concurrent/runnable.rb +103 -0
  16. data/lib/concurrent/supervisor.rb +271 -44
  17. data/lib/concurrent/thread_pool.rb +112 -39
  18. data/lib/concurrent/version.rb +1 -1
  19. data/md/executor.md +9 -3
  20. data/md/goroutine.md +11 -9
  21. data/md/reactor.md +32 -0
  22. data/md/supervisor.md +43 -0
  23. data/spec/concurrent/agent_spec.rb +128 -51
  24. data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
  25. data/spec/concurrent/channel_spec.rb +446 -0
  26. data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
  27. data/spec/concurrent/event_spec.rb +0 -19
  28. data/spec/concurrent/executor_spec.rb +167 -119
  29. data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
  30. data/spec/concurrent/functions_spec.rb +0 -20
  31. data/spec/concurrent/future_spec.rb +88 -0
  32. data/spec/concurrent/null_thread_pool_spec.rb +23 -2
  33. data/spec/concurrent/obligation_shared.rb +0 -5
  34. data/spec/concurrent/promise_spec.rb +9 -10
  35. data/spec/concurrent/runnable_shared.rb +62 -0
  36. data/spec/concurrent/runnable_spec.rb +233 -0
  37. data/spec/concurrent/supervisor_spec.rb +912 -47
  38. data/spec/concurrent/thread_pool_shared.rb +18 -31
  39. data/spec/spec_helper.rb +10 -3
  40. metadata +17 -23
  41. data/lib/concurrent/defer.rb +0 -65
  42. data/lib/concurrent/reactor.rb +0 -166
  43. data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
  44. data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
  45. data/lib/concurrent/utilities.rb +0 -32
  46. data/md/defer.md +0 -174
  47. data/spec/concurrent/defer_spec.rb +0 -199
  48. data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
  49. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
  50. data/spec/concurrent/reactor_spec.rb +0 -364
  51. data/spec/concurrent/utilities_spec.rb +0 -74
@@ -16,39 +16,30 @@ module Concurrent
16
16
  return @set == true
17
17
  end
18
18
 
19
- def set(pulse = false)
19
+ def set
20
20
  return true if set?
21
- @mutex.synchronize {
21
+ @mutex.synchronize do
22
22
  @set = true
23
- while @waiting > 0
24
- @notifier << :set
25
- @waiting -= 1
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
@@ -1,96 +1,92 @@
1
1
  require 'thread'
2
+ require 'concurrent/runnable'
2
3
 
3
4
  module Concurrent
4
5
 
5
- module Executor
6
- extend self
6
+ class Executor
7
+ include Runnable
8
+ behavior(:runnable)
7
9
 
8
- class ExecutionContext
9
- attr_reader :name
10
- attr_reader :execution_interval
11
- attr_reader :timeout_interval
10
+ EXECUTION_INTERVAL = 60
11
+ TIMEOUT_INTERVAL = 30
12
12
 
13
- protected
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
- def initialize(name, execution_interval, timeout_interval, thread)
16
- @name = name
17
- @execution_interval = execution_interval
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
- public
21
+ def initialize(name, opts = {}, &block)
22
+ raise ArgumentError.new('no block given') unless block_given?
24
23
 
25
- def status
26
- return @thread.status unless @thread.nil?
27
- end
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
- def join(limit = nil)
30
- if @thread.nil?
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
- def stop
40
- @thread[:stop] = true
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
- def kill
44
- unless @thread.nil?
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
- EXECUTION_INTERVAL = 60
54
- TIMEOUT_INTERVAL = 30
53
+ protected
55
54
 
56
- STDOUT_LOGGER = proc do |name, level, msg|
57
- print "%5s (%s) %s: %s\n" % [level.upcase, Time.now.strftime("%F %T"), name, msg]
55
+ def on_run
56
+ @monitor = Thread.current
58
57
  end
59
58
 
60
- def run(name, opts = {})
61
- raise ArgumentError.new('no block given') unless block_given?
62
-
63
- execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
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
- executor = Thread.new(*block_args) do |*args|
70
- sleep(execution_interval) unless run_now == true
71
- loop do
72
- break if Thread.current[:stop]
73
- begin
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
- executor.abort_on_exception = false
93
- return ExecutionContext.new(name, execution_interval, timeout_interval, executor)
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
- def self.new_fixed_thread_pool(size)
9
- return FixedThreadPool.new(size)
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
- if running?
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
- if running?
46
- @queue << [args, block]
47
- return true
48
- else
49
- return false
50
- end
51
- end
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
- # @private
85
- def collect_garbage # :nodoc:
86
- @collector = Thread.new do
87
- sleep(1)
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
@@ -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(initial, timeout = Concurrent::Agent::TIMEOUT)
11
- return Concurrent::Agent.new(initial, timeout)
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, timeout = nil)
32
+ def deref(object, *args, &block)
48
33
  if object.respond_to?(:deref)
49
- return object.deref(timeout)
34
+ return object.deref(*args, &block)
50
35
  elsif object.respond_to?(:value)
51
- return object.value(timeout)
36
+ return object.value(*args, &block)
52
37
  else
53
38
  raise ArgumentError.new('object does not support #deref')
54
39
  end
@@ -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
- mutex.synchronize do
32
- begin
33
- @value = yield(*args)
34
- @state = :fulfilled
35
- rescue Exception => ex
36
- @state = :rejected
37
- @reason = ex
38
- end
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