concurrent-ruby 0.1.0 → 0.1.1.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. 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