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