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
@@ -0,0 +1,162 @@
1
+ require 'thread'
2
+ require 'functional'
3
+ require 'concurrent/smart_mutex'
4
+
5
+ behavior_info(:sync_event_demux,
6
+ start: 0,
7
+ stop: 0,
8
+ stopped?: 0,
9
+ accept: 0,
10
+ respond: 2,
11
+ close: 0)
12
+
13
+ behavior_info(:async_event_demux,
14
+ start: 0,
15
+ stop: 0,
16
+ stopped?: 0,
17
+ set_reactor: 1)
18
+
19
+ behavior_info(:demux_reactor,
20
+ handle: -2)
21
+
22
+ module Concurrent
23
+
24
+ class Reactor
25
+
26
+ behavior(:demux_reactor)
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 = SmartMutex.new
47
+ end
48
+
49
+ def add_handler(event, &block)
50
+ event = event.to_sym
51
+ raise ArgumentError.new("'#{event}' is a reserved event") if RESERVED_EVENTS.include?(event)
52
+ raise ArgumentError.new('no block given') unless block_given?
53
+ @mutex.synchronize {
54
+ @handlers[event] = block
55
+ }
56
+ return true
57
+ end
58
+
59
+ def remove_handler(event)
60
+ handler = @mutex.synchronize {
61
+ @handlers.delete(event.to_sym)
62
+ }
63
+ return ! handler.nil?
64
+ end
65
+
66
+ def stop_on_signal(*signals)
67
+ signals.each{|signal| Signal.trap(signal){ self.stop } }
68
+ end
69
+
70
+ def handle_event(event, *args)
71
+ raise NotImplementedError.new("demultiplexer '#{@demux.class}' is synchronous") if @sync
72
+ return [:stopped, 'reactor not running'] unless running?
73
+ context = EventContext.new(event.to_sym, args.dup, Queue.new)
74
+ @queue.push(context)
75
+ return context.callback.pop
76
+ end
77
+ alias_method :handle, :handle_event
78
+
79
+ def running?
80
+ return @running
81
+ end
82
+
83
+ def start
84
+ raise StandardError.new('already running') if self.running?
85
+ @sync ? (@running = true; run_sync) : (@running = true; run_async)
86
+ end
87
+
88
+ def stop
89
+ return unless self.running?
90
+ if @sync
91
+ @demux.stop
92
+ else
93
+ @queue.push(:stop)
94
+ end
95
+ return nil
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
+ atomic {
123
+ @running = false
124
+ @demux.stop unless @demux.nil?
125
+ @demux = nil
126
+ }
127
+ end
128
+
129
+ def run_sync
130
+ @demux.start
131
+
132
+ loop do
133
+ break if @demux.stopped?
134
+ context = @demux.accept
135
+ if context.nil?
136
+ @demux.close
137
+ else
138
+ response = handle_event(context) do |result, message|
139
+ [result, message]
140
+ end
141
+ @demux.respond(*response)
142
+ end
143
+ end
144
+
145
+ finalize_stop
146
+ end
147
+
148
+ def run_async
149
+ @demux.start unless @demux.nil?
150
+
151
+ loop do
152
+ context = @queue.pop
153
+ break if context == :stop
154
+ handle_event(context) do |result, message|
155
+ context.callback.push([result, message])
156
+ end
157
+ end
158
+
159
+ finalize_stop
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,66 @@
1
+ require 'thread'
2
+ require 'concurrent/utilities'
3
+
4
+ module Concurrent
5
+
6
+ class SmartMutex
7
+
8
+ def initialize
9
+ @mutex = Mutex.new
10
+ end
11
+
12
+ def alone?
13
+ Thread.list.length <= 1
14
+ end
15
+
16
+ def lock
17
+ atomic do
18
+ @mutex.lock unless alone?
19
+ self
20
+ end
21
+ end
22
+
23
+ def locked?
24
+ atomic do
25
+ if alone?
26
+ false
27
+ else
28
+ @mutex.locked?
29
+ end
30
+ end
31
+ end
32
+
33
+ def sleep(timeout)
34
+ if alone?
35
+ Kernel.sleep(timeout)
36
+ else
37
+ @mutex.sleep(timeout)
38
+ end
39
+ end
40
+
41
+ def synchronize(&block)
42
+ if alone?
43
+ yield
44
+ else
45
+ @mutex.synchronize(&block)
46
+ end
47
+ end
48
+
49
+ def try_lock
50
+ atomic do
51
+ if alone?
52
+ true
53
+ else
54
+ @mutex.try_lock
55
+ end
56
+ end
57
+ end
58
+
59
+ def unlock
60
+ atomic do
61
+ @mutex.unlock unless alone?
62
+ self
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,96 @@
1
+ require 'socket'
2
+ require 'drb/acl'
3
+ require 'functional'
4
+ require 'concurrent/reactor'
5
+
6
+ module Concurrent
7
+
8
+ class TcpSyncDemux
9
+
10
+ behavior(:sync_event_demux)
11
+
12
+ DEFAULT_HOST = '127.0.0.1'
13
+ DEFAULT_PORT = 12345
14
+ DEFAULT_ACL = %[allow all]
15
+
16
+ def initialize(opts = {})
17
+ @host = opts[:host] || DEFAULT_HOST
18
+ @port = opts[:port] || DEFAULT_PORT
19
+ @acl = ACL.new(opts[:acl] || DEFAULT_ACL)
20
+ end
21
+
22
+ def start
23
+ @server = TCPServer.new(@host, @port)
24
+ end
25
+
26
+ def stop
27
+ atomic {
28
+ @socket.close unless @socket.nil?
29
+ @server.close unless @server.nil?
30
+ @server = @socket = nil
31
+ }
32
+ end
33
+
34
+ def stopped?
35
+ return @server.nil?
36
+ end
37
+
38
+ def accept
39
+ @socket = @server.accept if @socket.nil?
40
+ return nil unless @acl.allow_socket?(@socket)
41
+ event, args = get_message(@socket)
42
+ return nil if event.nil?
43
+ return Reactor::EventContext.new(event, args)
44
+ end
45
+
46
+ def respond(result, message)
47
+ return nil if @socket.nil?
48
+ @socket.puts(format_message(result, message))
49
+ end
50
+
51
+ def close
52
+ @socket.close
53
+ @socket = nil
54
+ end
55
+
56
+ def self.format_message(event, *args)
57
+ args = args.reduce('') do |memo, arg|
58
+ memo << "#{arg}\r\n"
59
+ end
60
+ return ":#{event}\r\n#{args}\r\n"
61
+ end
62
+ def format_message(*args) self.class.format_message(*args); end
63
+
64
+ def self.parse_message(message)
65
+ return atomic {
66
+ event = message.first.match /^:?(\w+)/
67
+ event = event[1].to_s.downcase.to_sym unless event.nil?
68
+
69
+ args = message.slice(1, message.length) || []
70
+
71
+ [event, args]
72
+ }
73
+ end
74
+ def parse_message(*args) self.class.parse_message(*args); end
75
+
76
+ def self.get_message(socket)
77
+ message = []
78
+ while line = socket.gets
79
+ if line.nil? || (line = line.strip).empty?
80
+ break
81
+ else
82
+ message << line
83
+ end
84
+ end
85
+
86
+ if message.empty?
87
+ return nil
88
+ else
89
+ return parse_message(message)
90
+ end
91
+ end
92
+ def get_message(*args) self.class.get_message(*args); end
93
+ end
94
+
95
+ TcpSyncDemultiplexer = TcpSyncDemux
96
+ end
@@ -1,61 +1,65 @@
1
- require 'functional/behavior'
2
- require 'concurrent/event'
3
-
4
- behavior_info(:thread_pool,
5
- running?: 0,
6
- shutdown?: 0,
7
- killed?: 0,
8
- shutdown: 0,
9
- kill: 0,
10
- size: 0,
11
- wait_for_termination: -1,
12
- post: -1,
13
- :<< => 1,
14
- status: 0)
15
-
16
- behavior_info(:global_thread_pool,
17
- post: -1,
18
- :<< => 1)
19
-
20
- module Concurrent
21
-
22
- class ThreadPool
23
-
24
- def initialize
25
- @status = :running
26
- @queue = Queue.new
27
- @termination = Event.new
28
- @pool = []
29
- end
30
-
31
- def running?
32
- return @status == :running
33
- end
34
-
35
- def shutdown?
36
- return ! running?
37
- end
38
-
39
- def killed?
40
- return @status == :killed
41
- end
42
-
43
- def shutdown
44
- @pool.size.times{ @queue << :stop }
45
- @status = :shuttingdown
46
- end
47
-
48
- def wait_for_termination(timeout = nil)
49
- if shutdown? || killed?
50
- return true
51
- else
52
- return @termination.wait(timeout)
53
- end
54
- end
55
-
56
- def <<(block)
57
- self.post(&block)
58
- return self
59
- end
60
- end
61
- end
1
+ require 'functional/behavior'
2
+
3
+ require 'concurrent/event'
4
+ require 'concurrent/utilities'
5
+
6
+ behavior_info(:thread_pool,
7
+ running?: 0,
8
+ shutdown?: 0,
9
+ killed?: 0,
10
+ shutdown: 0,
11
+ kill: 0,
12
+ size: 0,
13
+ wait_for_termination: -1,
14
+ post: -1,
15
+ :<< => 1,
16
+ status: 0)
17
+
18
+ behavior_info(:global_thread_pool,
19
+ post: -1,
20
+ :<< => 1)
21
+
22
+ module Concurrent
23
+
24
+ class ThreadPool
25
+
26
+ def initialize
27
+ @status = :running
28
+ @queue = Queue.new
29
+ @termination = Event.new
30
+ @pool = []
31
+ end
32
+
33
+ def running?
34
+ return @status == :running
35
+ end
36
+
37
+ def shutdown?
38
+ return ! running?
39
+ end
40
+
41
+ def killed?
42
+ return @status == :killed
43
+ end
44
+
45
+ def shutdown
46
+ atomic {
47
+ @pool.size.times{ @queue << :stop }
48
+ @status = :shuttingdown
49
+ }
50
+ end
51
+
52
+ def wait_for_termination(timeout = nil)
53
+ if shutdown? || killed?
54
+ return true
55
+ else
56
+ return @termination.wait(timeout)
57
+ end
58
+ end
59
+
60
+ def <<(block)
61
+ self.post(&block)
62
+ return self
63
+ end
64
+ end
65
+ end