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.
- data/LICENSE +21 -21
- data/README.md +279 -224
- data/lib/concurrent.rb +27 -20
- data/lib/concurrent/agent.rb +106 -130
- data/lib/concurrent/cached_thread_pool.rb +130 -122
- data/lib/concurrent/defer.rb +67 -69
- data/lib/concurrent/drb_async_demux.rb +72 -0
- data/lib/concurrent/event.rb +60 -60
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
- data/lib/concurrent/executor.rb +87 -0
- data/lib/concurrent/fixed_thread_pool.rb +89 -89
- data/lib/concurrent/functions.rb +120 -0
- data/lib/concurrent/future.rb +52 -42
- data/lib/concurrent/global_thread_pool.rb +3 -3
- data/lib/concurrent/goroutine.rb +29 -25
- data/lib/concurrent/obligation.rb +67 -121
- data/lib/concurrent/promise.rb +172 -194
- data/lib/concurrent/reactor.rb +162 -0
- data/lib/concurrent/smart_mutex.rb +66 -0
- data/lib/concurrent/tcp_sync_demux.rb +96 -0
- data/lib/concurrent/thread_pool.rb +65 -61
- data/lib/concurrent/utilities.rb +34 -0
- data/lib/concurrent/version.rb +3 -3
- data/lib/concurrent_ruby.rb +1 -1
- data/md/agent.md +123 -123
- data/md/defer.md +174 -174
- data/md/event.md +32 -32
- data/md/executor.md +176 -0
- data/md/future.md +83 -83
- data/md/goroutine.md +52 -52
- data/md/obligation.md +32 -32
- data/md/promise.md +225 -225
- data/md/thread_pool.md +197 -197
- data/spec/concurrent/agent_spec.rb +376 -405
- data/spec/concurrent/cached_thread_pool_spec.rb +112 -112
- data/spec/concurrent/defer_spec.rb +209 -199
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +250 -246
- data/spec/concurrent/event_spec.rb +134 -134
- data/spec/concurrent/executor_spec.rb +146 -0
- data/spec/concurrent/fixed_thread_pool_spec.rb +84 -84
- data/spec/concurrent/functions_spec.rb +57 -0
- data/spec/concurrent/future_spec.rb +125 -115
- data/spec/concurrent/goroutine_spec.rb +67 -52
- data/spec/concurrent/obligation_shared.rb +121 -121
- data/spec/concurrent/promise_spec.rb +299 -310
- data/spec/concurrent/smart_mutex_spec.rb +234 -0
- data/spec/concurrent/thread_pool_shared.rb +209 -209
- data/spec/concurrent/utilities_spec.rb +74 -0
- data/spec/spec_helper.rb +21 -19
- metadata +38 -14
- 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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
shutdown
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@
|
28
|
-
@
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|