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.
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