concurrent-ruby 0.1.1 → 0.2.0

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