concurrent-ruby 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -1
  3. data/lib/concurrent.rb +8 -1
  4. data/lib/concurrent/agent.rb +19 -40
  5. data/lib/concurrent/cached_thread_pool.rb +10 -11
  6. data/lib/concurrent/defer.rb +8 -12
  7. data/lib/concurrent/executor.rb +95 -0
  8. data/lib/concurrent/fixed_thread_pool.rb +12 -6
  9. data/lib/concurrent/functions.rb +120 -0
  10. data/lib/concurrent/future.rb +8 -20
  11. data/lib/concurrent/global_thread_pool.rb +13 -0
  12. data/lib/concurrent/goroutine.rb +5 -1
  13. data/lib/concurrent/null_thread_pool.rb +22 -0
  14. data/lib/concurrent/obligation.rb +10 -64
  15. data/lib/concurrent/promise.rb +38 -60
  16. data/lib/concurrent/reactor.rb +166 -0
  17. data/lib/concurrent/reactor/drb_async_demux.rb +83 -0
  18. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -0
  19. data/lib/concurrent/supervisor.rb +100 -0
  20. data/lib/concurrent/thread_pool.rb +16 -5
  21. data/lib/concurrent/utilities.rb +8 -0
  22. data/lib/concurrent/version.rb +1 -1
  23. data/md/defer.md +4 -4
  24. data/md/executor.md +187 -0
  25. data/md/promise.md +2 -0
  26. data/md/thread_pool.md +27 -0
  27. data/spec/concurrent/agent_spec.rb +8 -27
  28. data/spec/concurrent/cached_thread_pool_spec.rb +14 -1
  29. data/spec/concurrent/defer_spec.rb +17 -21
  30. data/spec/concurrent/event_machine_defer_proxy_spec.rb +159 -149
  31. data/spec/concurrent/executor_spec.rb +200 -0
  32. data/spec/concurrent/fixed_thread_pool_spec.rb +2 -3
  33. data/spec/concurrent/functions_spec.rb +217 -0
  34. data/spec/concurrent/future_spec.rb +4 -11
  35. data/spec/concurrent/global_thread_pool_spec.rb +38 -0
  36. data/spec/concurrent/goroutine_spec.rb +15 -0
  37. data/spec/concurrent/null_thread_pool_spec.rb +54 -0
  38. data/spec/concurrent/obligation_shared.rb +127 -116
  39. data/spec/concurrent/promise_spec.rb +16 -14
  40. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -0
  41. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -0
  42. data/spec/concurrent/reactor_spec.rb +364 -0
  43. data/spec/concurrent/supervisor_spec.rb +258 -0
  44. data/spec/concurrent/thread_pool_shared.rb +156 -161
  45. data/spec/concurrent/utilities_spec.rb +30 -1
  46. data/spec/spec_helper.rb +13 -0
  47. metadata +38 -9
@@ -0,0 +1,166 @@
1
+ require 'thread'
2
+ require 'functional'
3
+ require 'concurrent/supervisor'
4
+
5
+ behavior_info(:sync_event_demux,
6
+ run: 0,
7
+ stop: 0,
8
+ running?: 0,
9
+ accept: 0,
10
+ respond: 2)
11
+
12
+ behavior_info(:async_event_demux,
13
+ run: 0,
14
+ stop: 0,
15
+ running?: 0,
16
+ set_reactor: 1)
17
+
18
+ behavior_info(:demux_reactor,
19
+ handle: -2)
20
+
21
+ module Concurrent
22
+
23
+ class Reactor
24
+
25
+ behavior(:demux_reactor)
26
+ behavior(:runnable)
27
+
28
+ RESERVED_EVENTS = [ :stop ]
29
+
30
+ EventContext = Struct.new(:event, :args, :callback)
31
+
32
+ def initialize(demux = nil)
33
+ @demux = demux
34
+ if @demux.nil? || @demux.behaves_as?(:async_event_demux)
35
+ @sync = false
36
+ @queue = Queue.new
37
+ @demux.set_reactor(self) unless @demux.nil?
38
+ elsif @demux.behaves_as?(:sync_event_demux)
39
+ @sync = true
40
+ else
41
+ raise ArgumentError.new("invalid event demultiplexer '#{@demux}'")
42
+ end
43
+
44
+ @running = false
45
+ @handlers = Hash.new
46
+ @mutex = Mutex.new
47
+ end
48
+
49
+ def running?
50
+ return @running
51
+ end
52
+
53
+ def add_handler(event, &block)
54
+ raise ArgumentError.new('no block given') unless block_given?
55
+ event = event.to_sym
56
+ raise ArgumentError.new("'#{event}' is a reserved event") if RESERVED_EVENTS.include?(event)
57
+ @mutex.synchronize {
58
+ @handlers[event] = block
59
+ }
60
+ return true
61
+ end
62
+
63
+ def remove_handler(event)
64
+ handler = @mutex.synchronize {
65
+ @handlers.delete(event.to_sym)
66
+ }
67
+ return ! handler.nil?
68
+ end
69
+
70
+ def stop_on_signal(*signals)
71
+ signals.each{|signal| Signal.trap(signal){ Thread.new{ self.stop }}}
72
+ end
73
+
74
+ def handle(event, *args)
75
+ raise NotImplementedError.new("demultiplexer '#{@demux.class}' is synchronous") if @sync
76
+ return [:stopped, 'reactor not running'] unless running?
77
+ context = EventContext.new(event.to_sym, args.dup, Queue.new)
78
+ @queue.push(context)
79
+ return context.callback.pop
80
+ end
81
+
82
+ def run
83
+ raise StandardError.new('already running') if self.running?
84
+ @sync ? (@running = true; run_sync) : (@running = true; run_async)
85
+ end
86
+ alias_method :run, :run
87
+
88
+ def stop
89
+ return true unless self.running?
90
+ if @sync
91
+ @demux.stop
92
+ else
93
+ @queue.push(:stop)
94
+ end
95
+ return true
96
+ end
97
+
98
+ private
99
+
100
+ def handle_event(context)
101
+ raise ArgumentError.new('no block given') unless block_given?
102
+
103
+ handler = @mutex.synchronize {
104
+ @handlers[context.event]
105
+ }
106
+
107
+ if handler.nil?
108
+ response = yield(:noop, "'#{context.event}' handler not found")
109
+ else
110
+ begin
111
+ result = handler.call(*context.args)
112
+ response = yield(:ok, result)
113
+ rescue Exception => ex
114
+ response = yield(:ex, ex)
115
+ end
116
+ end
117
+
118
+ return response
119
+ end
120
+
121
+ def finalize_stop
122
+ @mutex.synchronize do
123
+ @running = false
124
+ @demux.stop unless @demux.nil?
125
+ @demux = nil
126
+ end
127
+ end
128
+
129
+ def run_sync
130
+ @demux.run
131
+
132
+ loop do
133
+ break unless @demux.running?
134
+ context = @demux.accept
135
+ begin
136
+ if context.nil?
137
+ @demux.stop
138
+ else
139
+ response = handle_event(context) do |result, message|
140
+ [result, message]
141
+ end
142
+ @demux.respond(*response)
143
+ end
144
+ rescue Exception => ex
145
+ @demux.respond(:abend, ex)
146
+ end
147
+ end
148
+
149
+ finalize_stop
150
+ end
151
+
152
+ def run_async
153
+ @demux.run unless @demux.nil?
154
+
155
+ loop do
156
+ context = @queue.pop
157
+ break if context == :stop
158
+ handle_event(context) do |result, message|
159
+ context.callback.push([result, message])
160
+ end
161
+ end
162
+
163
+ finalize_stop
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,83 @@
1
+ require 'drb/drb'
2
+ require 'drb/acl'
3
+ require 'functional'
4
+ require 'concurrent/reactor'
5
+ require 'concurrent/supervisor'
6
+
7
+ module Concurrent
8
+ class Reactor
9
+
10
+ class DRbAsyncDemux
11
+
12
+ behavior(:async_event_demux)
13
+
14
+ DEFAULT_URI = 'druby://localhost:12345'
15
+ DEFAULT_ACL = %w{deny all allow 127.0.0.1}
16
+
17
+ attr_reader :uri
18
+ attr_reader :acl
19
+
20
+ def initialize(opts = {})
21
+ @uri = opts[:uri] || DEFAULT_URI
22
+ @acl = ACL.new(opts[:acl] || DEFAULT_ACL)
23
+ end
24
+
25
+ def set_reactor(reactor)
26
+ raise ArgumentError.new('invalid reactor') unless reactor.behaves_as?(:demux_reactor)
27
+ @reactor = reactor
28
+ end
29
+
30
+ def run
31
+ raise StandardError.new('already running') if running?
32
+ DRb.install_acl(@acl)
33
+ @service = DRb.start_service(@uri, Demultiplexer.new(@reactor))
34
+ end
35
+
36
+ def stop
37
+ @service = DRb.stop_service
38
+ end
39
+
40
+ def running?
41
+ return ! @service.nil?
42
+ end
43
+
44
+ private
45
+
46
+ class Demultiplexer
47
+
48
+ def initialize(reactor)
49
+ @reactor = reactor
50
+ end
51
+
52
+ Concurrent::Reactor::RESERVED_EVENTS.each do |event|
53
+ define_method(event){|*args| false }
54
+ end
55
+
56
+ def method_missing(method, *args, &block)
57
+ (class << self; self; end).class_eval do
58
+ define_method(method) do |*args|
59
+ begin
60
+ result = @reactor.handle(method, *args)
61
+ rescue Exception => ex
62
+ raise DRb::DRbRemoteError.new(ex)
63
+ end
64
+ case result.first
65
+ when :ok
66
+ return result.last
67
+ when :ex
68
+ raise result.last
69
+ when :noop
70
+ raise NoMethodError.new("undefined method '#{method}' for #{self}")
71
+ else
72
+ raise DRb::DRbError.new("unexpected response from method '#{method}'")
73
+ end
74
+ end
75
+ end
76
+ self.send(method, *args)
77
+ end
78
+ end
79
+ end
80
+
81
+ DRbAsyncDemultiplexer = DRbAsyncDemux
82
+ end
83
+ end
@@ -0,0 +1,131 @@
1
+ require 'socket'
2
+ require 'drb/acl'
3
+ require 'functional'
4
+ require 'concurrent/reactor'
5
+ require 'concurrent/supervisor'
6
+
7
+ module Concurrent
8
+ class Reactor
9
+
10
+ class TcpSyncDemux
11
+
12
+ behavior(:sync_event_demux)
13
+
14
+ DEFAULT_HOST = '127.0.0.1'
15
+ DEFAULT_PORT = 12345
16
+ DEFAULT_ACL = %w{deny all allow 127.0.0.1}
17
+
18
+ attr_reader :host
19
+ attr_reader :port
20
+ attr_reader :acl
21
+
22
+ def initialize(opts = {})
23
+ @host = opts[:host] || DEFAULT_HOST
24
+ @port = opts[:port] || DEFAULT_PORT
25
+ @acl = ACL.new(opts[:acl] || DEFAULT_ACL)
26
+ end
27
+
28
+ def run
29
+ raise StandardError.new('already running') if running?
30
+ begin
31
+ @server = TCPServer.new(@host, @port)
32
+ return true
33
+ rescue Exception => ex
34
+ return false
35
+ end
36
+ end
37
+
38
+ def stop
39
+ begin
40
+ @socket.close unless @socket.nil?
41
+ rescue Exception => ex
42
+ # suppress
43
+ end
44
+
45
+ begin
46
+ @server.close unless @server.nil?
47
+ rescue Exception => ex
48
+ # suppress
49
+ end
50
+
51
+ @server = @socket = nil
52
+ return true
53
+ end
54
+
55
+ def reset
56
+ stop
57
+ sleep(1)
58
+ run
59
+ end
60
+
61
+ def running?
62
+ return ! @server.nil?
63
+ end
64
+
65
+ def accept
66
+ @socket = @server.accept if @socket.nil?
67
+ return nil unless @acl.allow_socket?(@socket)
68
+ event, args = get_message(@socket)
69
+ return nil if event.nil?
70
+ return Reactor::EventContext.new(event, args)
71
+ rescue Exception => ex
72
+ reset
73
+ return nil
74
+ end
75
+
76
+ def respond(result, message)
77
+ return nil if @socket.nil?
78
+ @socket.puts(format_message(result, message))
79
+ rescue Exception => ex
80
+ reset
81
+ end
82
+
83
+ def self.format_message(event, *args)
84
+ event = event.to_s.strip
85
+ raise ArgumentError.new('nil or empty event') if event.empty?
86
+ args = args.reduce('') do |memo, arg|
87
+ memo << "#{arg}\r\n"
88
+ end
89
+ return "#{event}\r\n#{args}\r\n"
90
+ end
91
+
92
+ def format_message(*args)
93
+ self.class.format_message(*args)
94
+ end
95
+
96
+ def self.parse_message(message)
97
+ message = message.lines.map(&:chomp) if message.is_a?(String)
98
+ return [nil, []] if message.nil?
99
+ event = message.first.match(/^:?(\w+)/)
100
+ event = event[1].to_s.downcase.to_sym unless event.nil?
101
+ args = message.slice(1, message.length) || []
102
+ args.pop if args.last.nil? || args.last.empty?
103
+ return [event, args]
104
+ end
105
+
106
+ def parse_message(*args)
107
+ self.class.parse_message(*args)
108
+ end
109
+
110
+ def self.get_message(socket)
111
+ message = []
112
+ while line = socket.gets
113
+ if line.nil? || (line = line.strip).empty?
114
+ break
115
+ else
116
+ message << line
117
+ end
118
+ end
119
+
120
+ if message.empty?
121
+ return nil
122
+ else
123
+ return parse_message(message)
124
+ end
125
+ end
126
+ def get_message(*args) self.class.get_message(*args); end
127
+ end
128
+
129
+ TcpSyncDemultiplexer = TcpSyncDemux
130
+ end
131
+ end
@@ -0,0 +1,100 @@
1
+ require 'thread'
2
+ require 'functional'
3
+
4
+ behavior_info(:runnable,
5
+ run: 0,
6
+ stop: 0,
7
+ running?: 0)
8
+
9
+ module Concurrent
10
+
11
+ class Supervisor
12
+
13
+ DEFAULT_MONITOR_INTERVAL = 1
14
+
15
+ behavior(:runnable)
16
+
17
+ WorkerContext = Struct.new(:worker, :thread)
18
+
19
+ attr_reader :monitor_interval
20
+
21
+ def initialize(opts = {})
22
+ @mutex = Mutex.new
23
+ @workers = []
24
+ @running = false
25
+ @monitor = nil
26
+ @monitor_interval = opts[:monitor] || opts[:monitor_interval] || DEFAULT_MONITOR_INTERVAL
27
+ add_worker(opts[:worker]) unless opts[:worker].nil?
28
+ end
29
+
30
+ def run!
31
+ raise StandardError.new('already running') if running?
32
+ @running = true
33
+ @monitor = Thread.new{ monitor }
34
+ Thread.pass
35
+ end
36
+
37
+ def run
38
+ raise StandardError.new('already running') if running?
39
+ @running = true
40
+ monitor
41
+ end
42
+
43
+ def stop
44
+ return true unless running?
45
+ @running = false
46
+ @mutex.synchronize do
47
+ Thread.kill(@monitor) unless @monitor.nil?
48
+ @monitor = nil
49
+
50
+ until @workers.empty?
51
+ context = @workers.pop
52
+ begin
53
+ context.worker.stop
54
+ Thread.pass
55
+ rescue Exception => ex
56
+ # suppress
57
+ ensure
58
+ Thread.kill(context.thread) unless context.thread.nil?
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def running?
65
+ return @running
66
+ end
67
+
68
+ def length
69
+ return @workers.length
70
+ end
71
+ alias_method :size, :length
72
+
73
+ def add_worker(worker)
74
+ if worker.nil? || running? || ! worker.behaves_as?(:runnable)
75
+ return false
76
+ else
77
+ @mutex.synchronize {
78
+ @workers << WorkerContext.new(worker)
79
+ }
80
+ return true
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def monitor
87
+ loop do
88
+ @mutex.synchronize do
89
+ @workers.each do |context|
90
+ unless context.thread && context.thread.alive?
91
+ context.thread = Thread.new{ context.worker.run }
92
+ context.thread.abort_on_exception = false
93
+ end
94
+ end
95
+ end
96
+ sleep(@monitor_interval)
97
+ end
98
+ end
99
+ end
100
+ end