concurrent-ruby 0.1.0 → 0.1.1.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. data/LICENSE +21 -21
  2. data/README.md +279 -224
  3. data/lib/concurrent.rb +27 -20
  4. data/lib/concurrent/agent.rb +106 -130
  5. data/lib/concurrent/cached_thread_pool.rb +130 -122
  6. data/lib/concurrent/defer.rb +67 -69
  7. data/lib/concurrent/drb_async_demux.rb +72 -0
  8. data/lib/concurrent/event.rb +60 -60
  9. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  10. data/lib/concurrent/executor.rb +87 -0
  11. data/lib/concurrent/fixed_thread_pool.rb +89 -89
  12. data/lib/concurrent/functions.rb +120 -0
  13. data/lib/concurrent/future.rb +52 -42
  14. data/lib/concurrent/global_thread_pool.rb +3 -3
  15. data/lib/concurrent/goroutine.rb +29 -25
  16. data/lib/concurrent/obligation.rb +67 -121
  17. data/lib/concurrent/promise.rb +172 -194
  18. data/lib/concurrent/reactor.rb +162 -0
  19. data/lib/concurrent/smart_mutex.rb +66 -0
  20. data/lib/concurrent/tcp_sync_demux.rb +96 -0
  21. data/lib/concurrent/thread_pool.rb +65 -61
  22. data/lib/concurrent/utilities.rb +34 -0
  23. data/lib/concurrent/version.rb +3 -3
  24. data/lib/concurrent_ruby.rb +1 -1
  25. data/md/agent.md +123 -123
  26. data/md/defer.md +174 -174
  27. data/md/event.md +32 -32
  28. data/md/executor.md +176 -0
  29. data/md/future.md +83 -83
  30. data/md/goroutine.md +52 -52
  31. data/md/obligation.md +32 -32
  32. data/md/promise.md +225 -225
  33. data/md/thread_pool.md +197 -197
  34. data/spec/concurrent/agent_spec.rb +376 -405
  35. data/spec/concurrent/cached_thread_pool_spec.rb +112 -112
  36. data/spec/concurrent/defer_spec.rb +209 -199
  37. data/spec/concurrent/event_machine_defer_proxy_spec.rb +250 -246
  38. data/spec/concurrent/event_spec.rb +134 -134
  39. data/spec/concurrent/executor_spec.rb +146 -0
  40. data/spec/concurrent/fixed_thread_pool_spec.rb +84 -84
  41. data/spec/concurrent/functions_spec.rb +57 -0
  42. data/spec/concurrent/future_spec.rb +125 -115
  43. data/spec/concurrent/goroutine_spec.rb +67 -52
  44. data/spec/concurrent/obligation_shared.rb +121 -121
  45. data/spec/concurrent/promise_spec.rb +299 -310
  46. data/spec/concurrent/smart_mutex_spec.rb +234 -0
  47. data/spec/concurrent/thread_pool_shared.rb +209 -209
  48. data/spec/concurrent/utilities_spec.rb +74 -0
  49. data/spec/spec_helper.rb +21 -19
  50. metadata +38 -14
  51. checksums.yaml +0 -7
@@ -1,20 +1,27 @@
1
- require 'thread'
2
-
3
- require 'concurrent/version'
4
-
5
- require 'concurrent/event'
6
-
7
- require 'concurrent/agent'
8
- require 'concurrent/defer'
9
- require 'concurrent/future'
10
- require 'concurrent/goroutine'
11
- require 'concurrent/promise'
12
- require 'concurrent/obligation'
13
-
14
- require 'concurrent/thread_pool'
15
- require 'concurrent/cached_thread_pool'
16
- require 'concurrent/fixed_thread_pool'
17
-
18
- require 'concurrent/global_thread_pool'
19
-
20
- require 'concurrent/event_machine_defer_proxy' if defined?(EventMachine)
1
+ require 'thread'
2
+
3
+ require 'concurrent/version'
4
+
5
+ require 'concurrent/event'
6
+
7
+ require 'concurrent/agent'
8
+ require 'concurrent/defer'
9
+ require 'concurrent/executor'
10
+ require 'concurrent/future'
11
+ require 'concurrent/goroutine'
12
+ require 'concurrent/promise'
13
+ require 'concurrent/obligation'
14
+ require 'concurrent/reactor'
15
+ require 'concurrent/smart_mutex'
16
+ require 'concurrent/utilities'
17
+
18
+ require 'concurrent/drb_async_demux'
19
+ require 'concurrent/tcp_sync_demux'
20
+
21
+ require 'concurrent/thread_pool'
22
+ require 'concurrent/cached_thread_pool'
23
+ require 'concurrent/fixed_thread_pool'
24
+
25
+ require 'concurrent/global_thread_pool'
26
+
27
+ require 'concurrent/event_machine_defer_proxy' if defined?(EventMachine)
@@ -1,130 +1,106 @@
1
- require 'observer'
2
- require 'thread'
3
-
4
- require 'concurrent/global_thread_pool'
5
-
6
- module Concurrent
7
-
8
- # An agent is a single atomic value that represents an identity. The current value
9
- # of the agent can be requested at any time (#deref). Each agent has a work queue and operates on
10
- # the global thread pool. Consumers can #post code blocks to the agent. The code block (function)
11
- # will receive the current value of the agent as its sole parameter. The return value of the block
12
- # will become the new value of the agent. Agents support two error handling modes: fail and continue.
13
- # A good example of an agent is a shared incrementing counter, such as the score in a video game.
14
- class Agent
15
- include Observable
16
-
17
- TIMEOUT = 5
18
-
19
- attr_reader :initial
20
- attr_reader :timeout
21
-
22
- def initialize(initial, timeout = TIMEOUT)
23
- @value = initial
24
- @timeout = timeout
25
- @rescuers = []
26
- @validator = nil
27
- @queue = Queue.new
28
-
29
- $GLOBAL_THREAD_POOL << proc{ work }
30
- end
31
-
32
- def value(timeout = 0) return @value; end
33
- alias_method :deref, :value
34
-
35
- def rescue(clazz = Exception, &block)
36
- @rescuers << Rescuer.new(clazz, block) if block_given?
37
- return self
38
- end
39
- alias_method :catch, :rescue
40
- alias_method :on_error, :rescue
41
-
42
- def validate(&block)
43
- @validator = block if block_given?
44
- return self
45
- end
46
- alias_method :validates, :validate
47
- alias_method :validate_with, :validate
48
- alias_method :validates_with, :validate
49
-
50
- def post(&block)
51
- return @queue.length unless block_given?
52
- @queue << block
53
- return @queue.length
54
- end
55
-
56
- def <<(block)
57
- self.post(&block)
58
- return self
59
- end
60
-
61
- def length
62
- @queue.length
63
- end
64
- alias_method :size, :length
65
- alias_method :count, :length
66
-
67
- alias_method :add_watch, :add_observer
68
-
69
- private
70
-
71
- # @private
72
- Rescuer = Struct.new(:clazz, :block)
73
-
74
- # @private
75
- def try_rescue(ex) # :nodoc:
76
- rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
77
- rescuer.block.call(ex) if rescuer
78
- rescue Exception => e
79
- # supress
80
- end
81
-
82
- # @private
83
- def work # :nodoc:
84
- loop do
85
- Thread.pass
86
- handler = @queue.pop
87
- begin
88
- result = Timeout.timeout(@timeout){
89
- handler.call(@value)
90
- }
91
- if @validator.nil? || @validator.call(result)
92
- @value = result
93
- changed
94
- notify_observers(Time.now, @value)
95
- end
96
- rescue Exception => ex
97
- try_rescue(ex)
98
- end
99
- end
100
- end
101
- end
102
- end
103
-
104
- module Kernel
105
-
106
- def agent(initial, timeout = Concurrent::Agent::TIMEOUT)
107
- return Concurrent::Agent.new(initial, timeout)
108
- end
109
- module_function :agent
110
-
111
- def deref(agent, timeout = nil)
112
- if agent.respond_to?(:deref)
113
- return agent.deref(timeout)
114
- elsif agent.respond_to?(:value)
115
- return agent.deref(timeout)
116
- else
117
- return nil
118
- end
119
- end
120
- module_function :deref
121
-
122
- def post(agent, &block)
123
- if agent.respond_to?(:post)
124
- return agent.post(&block)
125
- else
126
- return nil
127
- end
128
- end
129
- module_function :deref
130
- end
1
+ require 'observer'
2
+ require 'thread'
3
+
4
+ require 'concurrent/utilities'
5
+
6
+ module Concurrent
7
+
8
+ # An agent is a single atomic value that represents an identity. The current value
9
+ # of the agent can be requested at any time (#deref). Each agent has a work queue and operates on
10
+ # the global thread pool. Consumers can #post code blocks to the agent. The code block (function)
11
+ # will receive the current value of the agent as its sole parameter. The return value of the block
12
+ # will become the new value of the agent. Agents support two error handling modes: fail and continue.
13
+ # A good example of an agent is a shared incrementing counter, such as the score in a video game.
14
+ class Agent
15
+ include Observable
16
+
17
+ TIMEOUT = 5
18
+
19
+ attr_reader :initial
20
+ attr_reader :timeout
21
+
22
+ def initialize(initial, timeout = TIMEOUT)
23
+ @value = initial
24
+ @timeout = timeout
25
+ @rescuers = []
26
+ @validator = nil
27
+ @queue = Queue.new
28
+
29
+ @thread = Thread.new{ work }
30
+ end
31
+
32
+ def value(timeout = 0) return @value; end
33
+ alias_method :deref, :value
34
+
35
+ def rescue(clazz = Exception, &block)
36
+ @rescuers << Rescuer.new(clazz, block) if block_given?
37
+ return self
38
+ end
39
+ alias_method :catch, :rescue
40
+ alias_method :on_error, :rescue
41
+
42
+ def validate(&block)
43
+ @validator = block if block_given?
44
+ return self
45
+ end
46
+ alias_method :validates, :validate
47
+ alias_method :validate_with, :validate
48
+ alias_method :validates_with, :validate
49
+
50
+ def post(&block)
51
+ return @queue.length unless block_given?
52
+ return atomic {
53
+ @queue << block
54
+ @queue.length
55
+ }
56
+ end
57
+
58
+ def <<(block)
59
+ self.post(&block)
60
+ return self
61
+ end
62
+
63
+ def length
64
+ @queue.length
65
+ end
66
+ alias_method :size, :length
67
+ alias_method :count, :length
68
+
69
+ alias_method :add_watch, :add_observer
70
+
71
+ private
72
+
73
+ # @private
74
+ Rescuer = Struct.new(:clazz, :block)
75
+
76
+ # @private
77
+ def try_rescue(ex) # :nodoc:
78
+ rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
79
+ rescuer.block.call(ex) if rescuer
80
+ rescue Exception => e
81
+ # supress
82
+ end
83
+
84
+ # @private
85
+ def work # :nodoc:
86
+ loop do
87
+ Thread.pass
88
+ handler = @queue.pop
89
+ begin
90
+ result = Timeout.timeout(@timeout){
91
+ handler.call(@value)
92
+ }
93
+ if @validator.nil? || @validator.call(result)
94
+ atomic {
95
+ @value = result
96
+ changed
97
+ }
98
+ notify_observers(Time.now, @value)
99
+ end
100
+ rescue Exception => ex
101
+ try_rescue(ex)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -1,122 +1,130 @@
1
- require 'thread'
2
-
3
- require 'concurrent/thread_pool'
4
- require 'functional/utilities'
5
-
6
- module Concurrent
7
-
8
- def self.new_cached_thread_pool
9
- return CachedThreadPool.new
10
- end
11
-
12
- class CachedThreadPool < ThreadPool
13
- behavior(:thread_pool)
14
-
15
- DEFAULT_GC_INTERVAL = 60
16
- DEFAULT_THREAD_IDLETIME = 60
17
-
18
- attr_reader :working
19
-
20
- def initialize(opts = {})
21
- @gc_interval = opts[:gc_interval] || DEFAULT_GC_INTERVAL
22
- @thread_idletime = opts[:thread_idletime] || DEFAULT_THREAD_IDLETIME
23
- super()
24
- @working = 0
25
- @mutex = Mutex.new
26
- end
27
-
28
- def kill
29
- @status = :killed
30
- @mutex.synchronize do
31
- @pool.each{|t| Thread.kill(t.thread) }
32
- end
33
- end
34
-
35
- def size
36
- return @pool.length
37
- end
38
-
39
- def post(*args, &block)
40
- raise ArgumentError.new('no block given') unless block_given?
41
- if running?
42
- collect_garbage if @pool.empty?
43
- @mutex.synchronize do
44
- if @working >= @pool.length
45
- create_worker_thread
46
- end
47
- @queue << [args, block]
48
- end
49
- return true
50
- else
51
- return false
52
- end
53
- end
54
-
55
- # @private
56
- def status # :nodoc:
57
- @mutex.synchronize do
58
- @pool.collect do |worker|
59
- [
60
- worker.status,
61
- worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
62
- worker.thread.status
63
- ]
64
- end
65
- end
66
- end
67
-
68
- private
69
-
70
- Worker = Struct.new(:status, :idletime, :thread)
71
-
72
- # @private
73
- def create_worker_thread # :nodoc:
74
- worker = Worker.new(:idle, timestamp, nil)
75
-
76
- worker.thread = Thread.new(worker) do |me|
77
-
78
- loop do
79
- task = @queue.pop
80
-
81
- @working += 1
82
- me.status = :working
83
-
84
- if task == :stop
85
- me.status = :stopping
86
- break
87
- else
88
- task.last.call(*task.first)
89
- @working -= 1
90
- me.status = :idle
91
- me.idletime = timestamp
92
- end
93
- end
94
-
95
- @pool.delete(me)
96
- if @pool.empty?
97
- @termination.set
98
- @status = :shutdown unless killed?
99
- end
100
- end
101
-
102
- @pool << worker
103
- end
104
-
105
- # @private
106
- def collect_garbage # :nodoc:
107
- @collector = Thread.new do
108
- loop do
109
- sleep(@gc_interval)
110
- @mutex.synchronize do
111
- @pool.reject! do |worker|
112
- worker.thread.status.nil? ||
113
- (worker.status == :idle && @thread_idletime >= delta(worker.idletime, timestamp))
114
- end
115
- end
116
- @working = @pool.count{|worker| worker.status == :working}
117
- break if @pool.empty?
118
- end
119
- end
120
- end
121
- end
122
- end
1
+ require 'thread'
2
+
3
+ require 'concurrent/thread_pool'
4
+ require 'concurrent/utilities'
5
+
6
+ require 'functional/utilities'
7
+
8
+ module Concurrent
9
+
10
+ def self.new_cached_thread_pool
11
+ return CachedThreadPool.new
12
+ end
13
+
14
+ class CachedThreadPool < ThreadPool
15
+ behavior(:thread_pool)
16
+
17
+ DEFAULT_GC_INTERVAL = 60
18
+ DEFAULT_THREAD_IDLETIME = 60
19
+
20
+ attr_reader :working
21
+
22
+ def initialize(opts = {})
23
+ @gc_interval = (opts[:gc_interval] || DEFAULT_GC_INTERVAL).freeze
24
+ @thread_idletime = (opts[:thread_idletime] || DEFAULT_THREAD_IDLETIME).freeze
25
+ super()
26
+ @working = 0
27
+ @mutex = Mutex.new
28
+ end
29
+
30
+ def kill
31
+ @status = :killed
32
+ @mutex.synchronize do
33
+ @pool.each{|t| Thread.kill(t.thread) }
34
+ end
35
+ end
36
+
37
+ def size
38
+ return @pool.length
39
+ end
40
+
41
+ def post(*args, &block)
42
+ raise ArgumentError.new('no block given') unless block_given?
43
+ if running?
44
+ collect_garbage if @pool.empty?
45
+ @mutex.synchronize do
46
+ if @working >= @pool.length
47
+ create_worker_thread
48
+ end
49
+ @queue << [args, block]
50
+ end
51
+ return true
52
+ else
53
+ return false
54
+ end
55
+ end
56
+
57
+ # @private
58
+ def status # :nodoc:
59
+ @mutex.synchronize do
60
+ @pool.collect do |worker|
61
+ [
62
+ worker.status,
63
+ worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
64
+ worker.thread.status
65
+ ]
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ Worker = Struct.new(:status, :idletime, :thread)
73
+
74
+ # @private
75
+ def create_worker_thread # :nodoc:
76
+ worker = Worker.new(:idle, timestamp, nil)
77
+
78
+ worker.thread = Thread.new(worker) do |me|
79
+
80
+ loop do
81
+ task = @queue.pop
82
+
83
+ atomic {
84
+ @working += 1
85
+ me.status = :working
86
+ }
87
+
88
+ if task == :stop
89
+ me.status = :stopping
90
+ break
91
+ else
92
+ task.last.call(*task.first)
93
+ atomic {
94
+ @working -= 1
95
+ me.status = :idle
96
+ me.idletime = timestamp
97
+ }
98
+ end
99
+ end
100
+
101
+ atomic {
102
+ @pool.delete(me)
103
+ if @pool.empty?
104
+ @termination.set
105
+ @status = :shutdown unless killed?
106
+ end
107
+ }
108
+ end
109
+
110
+ @pool << worker
111
+ end
112
+
113
+ # @private
114
+ def collect_garbage # :nodoc:
115
+ @collector = Thread.new do
116
+ loop do
117
+ sleep(@gc_interval)
118
+ @mutex.synchronize do
119
+ @pool.reject! do |worker|
120
+ worker.thread.status.nil? ||
121
+ (worker.status == :idle && @thread_idletime >= delta(worker.idletime, timestamp))
122
+ end
123
+ end
124
+ @working = @pool.count{|worker| worker.status == :working}
125
+ break if @pool.empty?
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end