asir 0.2.0 → 1.0.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/Gemfile +1 -2
- data/README.textile +4 -2
- data/VERSION +1 -1
- data/asir.gemspec +1 -4
- data/asir.riterate.yml +1 -0
- data/bin/asir +2 -1
- data/example/asir_control.sh +63 -1
- data/example/asir_control_client_http.rb +2 -2
- data/example/asir_control_client_resque.rb +16 -0
- data/example/asir_control_client_zmq.rb +3 -3
- data/example/config/asir_config.rb +20 -8
- data/example/ex02.rb +1 -1
- data/example/ex03.rb +2 -2
- data/example/ex04.rb +2 -2
- data/example/ex05.rb +1 -1
- data/example/ex06.rb +6 -5
- data/example/ex07.rb +2 -2
- data/example/ex08.rb +2 -2
- data/example/ex09.rb +2 -2
- data/example/ex10.rb +2 -2
- data/example/ex11.rb +5 -5
- data/example/ex12.rb +6 -6
- data/example/ex13.rb +4 -4
- data/example/ex14.rb +4 -4
- data/example/ex15.rb +2 -2
- data/example/ex16.rb +8 -8
- data/example/ex17.rb +12 -11
- data/example/ex18.rb +5 -5
- data/example/ex19.rb +3 -3
- data/example/ex20.rb +3 -3
- data/example/ex21.rb +3 -3
- data/example/ex22.rb +1 -1
- data/example/ex23.rb +2 -2
- data/example/ex24.rb +4 -4
- data/example/ex25.rb +41 -0
- data/example/example_helper.rb +38 -3
- data/example/sample_service.rb +4 -4
- data/hack_night/exercise/prob-3.rb +3 -3
- data/hack_night/exercise/prob-6.rb +2 -2
- data/hack_night/exercise/prob-7.rb +2 -2
- data/hack_night/solution/prob-2.rb +2 -2
- data/hack_night/solution/prob-3.rb +3 -3
- data/hack_night/solution/prob-6.rb +7 -6
- data/hack_night/solution/prob-7.rb +6 -6
- data/{spec → lab}/const_get_speed_spec.rb +0 -0
- data/lib/asir.rb +29 -7
- data/lib/asir/additional_data.rb +25 -0
- data/lib/asir/channel.rb +4 -5
- data/lib/asir/client.rb +29 -13
- data/lib/asir/config.rb +8 -0
- data/lib/asir/description.rb +34 -0
- data/lib/asir/environment.rb +96 -0
- data/lib/asir/error.rb +4 -1
- data/lib/asir/invoker.rb +14 -0
- data/lib/asir/main.rb +84 -103
- data/lib/asir/message.rb +1 -1
- data/lib/asir/poll_throttle.rb +53 -0
- data/lib/asir/retry_behavior.rb +1 -1
- data/lib/asir/thread_variable.rb +183 -0
- data/lib/asir/transport.rb +36 -23
- data/lib/asir/transport/beanstalk.rb +18 -52
- data/lib/asir/transport/conduit.rb +42 -0
- data/lib/asir/transport/connection_oriented.rb +32 -56
- data/lib/asir/transport/delegation.rb +5 -5
- data/lib/asir/transport/demux.rb +33 -0
- data/lib/asir/transport/file.rb +5 -3
- data/lib/asir/transport/payload_io.rb +8 -4
- data/lib/asir/transport/resque.rb +212 -0
- data/lib/asir/transport/stream.rb +19 -9
- data/lib/asir/transport/tcp_socket.rb +3 -2
- data/lib/asir/transport/zmq.rb +14 -17
- data/lib/asir/uri_config.rb +51 -0
- data/lib/asir/version.rb +1 -1
- data/spec/client_spec.rb +48 -0
- data/spec/demux_spec.rb +38 -0
- data/spec/json_spec.rb +0 -2
- data/spec/message_spec.rb +68 -0
- data/spec/performance_spec.rb +66 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/thread_variable_spec.rb +135 -0
- data/spec/transport_spec.rb +82 -0
- metadata +28 -4
data/lib/asir/transport/file.rb
CHANGED
@@ -55,22 +55,23 @@ module ASIR
|
|
55
55
|
def serve_file!
|
56
56
|
::File.open(file, "r") do | stream |
|
57
57
|
@running = true
|
58
|
-
|
58
|
+
_serve_stream! stream, nil # One-way: no result stream.
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
62
|
# !SLIDE
|
63
63
|
# Named Pipe Server
|
64
64
|
|
65
|
-
def
|
65
|
+
def prepare_server!
|
66
66
|
# _log [ :prepare_pipe_server!, file ]
|
67
67
|
unless ::File.exist? file
|
68
68
|
system(cmd = "mkfifo #{file.inspect}") or raise "cannot run #{cmd.inspect}"
|
69
69
|
::File.chmod(perms, file) rescue nil if perms
|
70
70
|
end
|
71
71
|
end
|
72
|
+
alias :prepare_pipe_server! :prepare_server!
|
72
73
|
|
73
|
-
def
|
74
|
+
def run_server!
|
74
75
|
# _log [ :run_pipe_server!, file ]
|
75
76
|
with_server_signals! do
|
76
77
|
@running = true
|
@@ -79,6 +80,7 @@ module ASIR
|
|
79
80
|
end
|
80
81
|
end
|
81
82
|
end
|
83
|
+
alias :run_pipe_server! :run_server!
|
82
84
|
|
83
85
|
# !SLIDE END
|
84
86
|
end
|
@@ -4,25 +4,29 @@ module ASIR
|
|
4
4
|
# Payload IO for Transport
|
5
5
|
#
|
6
6
|
# Framing
|
7
|
-
# *
|
7
|
+
# * Header line containing the number of bytes in the payload.
|
8
8
|
# * The payload bytes.
|
9
9
|
# * Blank line.
|
10
|
+
# * Footer.
|
10
11
|
module PayloadIO
|
11
12
|
class UnexpectedResponse < Error; end
|
12
13
|
|
13
|
-
|
14
|
+
HEADER = "# asir_payload_size: "
|
15
|
+
FOOTER = "\n# asir_payload_end"
|
14
16
|
|
15
17
|
def _write payload, stream
|
18
|
+
stream.write HEADER
|
16
19
|
stream.puts payload.size
|
17
20
|
stream.write payload
|
18
|
-
stream.
|
21
|
+
stream.puts FOOTER
|
19
22
|
stream.flush
|
20
23
|
stream
|
21
24
|
end
|
22
25
|
|
23
26
|
def _read stream
|
24
|
-
size = stream.readline.chomp.to_i
|
27
|
+
size = /\d+$/.match(stream.readline.chomp)[0].to_i # HEADER (size)
|
25
28
|
payload = stream.read(size)
|
29
|
+
stream.readline # FOOTER
|
26
30
|
stream.readline
|
27
31
|
payload
|
28
32
|
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'asir/transport/connection_oriented'
|
2
|
+
require 'resque'
|
3
|
+
require 'asir/poll_throttle'
|
4
|
+
|
5
|
+
module ASIR
|
6
|
+
class Transport
|
7
|
+
# !SLIDE
|
8
|
+
# Resque Transport
|
9
|
+
class Resque < ConnectionOriented
|
10
|
+
include PollThrottle
|
11
|
+
|
12
|
+
attr_accessor :queues, :queue, :namespace, :throttle
|
13
|
+
|
14
|
+
def initialize *args
|
15
|
+
@port_default = 6379
|
16
|
+
@scheme_default = 'redis'.freeze
|
17
|
+
super
|
18
|
+
self.one_way = true
|
19
|
+
# Reraise exception, let Resque::Worker handle it.
|
20
|
+
@on_exeception ||= lambda do | trans, exc, type, message |
|
21
|
+
raise exc, exc.backtrace
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# !SLIDE
|
26
|
+
# Resque client.
|
27
|
+
def _client_connect!
|
28
|
+
# $stderr.puts " #{$$} #{self} _client_connect!"
|
29
|
+
resque_connect!
|
30
|
+
rescue ::Exception => exc
|
31
|
+
raise exc.class, "#{self.class} #{uri}: #{exc.message}", exc.backtrace
|
32
|
+
end
|
33
|
+
|
34
|
+
# !SLIDE
|
35
|
+
# Resque server (worker).
|
36
|
+
def _server!
|
37
|
+
resque_connect!
|
38
|
+
resque_worker
|
39
|
+
rescue ::Exception => exc
|
40
|
+
raise exc.class, "#{self.class} #{uri}: #{exc.message}", exc.backtrace
|
41
|
+
end
|
42
|
+
|
43
|
+
def _receive_result message, opaque_result
|
44
|
+
return nil if one_way || message.one_way
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def _send_result message, result, result_payload, stream, message_state
|
49
|
+
return nil if one_way || message.one_way
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
def _send_message message, message_payload
|
54
|
+
stream.with_stream! do | io | # Force connect
|
55
|
+
$stderr.puts " #{self} _send_message #{message_payload.inspect} to queue=#{queue.inspect} as #{self.class} :process_job" if @verbose >= 2
|
56
|
+
::Resque.enqueue_to(queue, self.class, message_payload)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def queues
|
61
|
+
@queues ||=
|
62
|
+
(
|
63
|
+
x = nil
|
64
|
+
x = path if @uri
|
65
|
+
x ||= ""
|
66
|
+
root, x = x.split('/')
|
67
|
+
x ||= ""
|
68
|
+
x = x.split(/(\s+|\s*,\s*)/)
|
69
|
+
x.each(&:freeze)
|
70
|
+
x.freeze
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Defaults to [ 'asir' ].
|
75
|
+
def queues_
|
76
|
+
@queues_ ||=
|
77
|
+
queues.empty? ? [ DEFAULT_QUEUE ] : queues.freeze
|
78
|
+
end
|
79
|
+
|
80
|
+
# Defaults to 'asir'.
|
81
|
+
def queue
|
82
|
+
@queue ||= queues_.first || DEFAULT_QUEUE
|
83
|
+
end
|
84
|
+
|
85
|
+
# Defaults to 'asir'.
|
86
|
+
def namespace_
|
87
|
+
@namespace_ ||= namespace || DEFAULT_QUEUE
|
88
|
+
end
|
89
|
+
|
90
|
+
DEFAULT_QUEUE = 'asir'.freeze
|
91
|
+
|
92
|
+
def _server_accept_connection! server
|
93
|
+
[ server, server ]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Resque is message-oriented, process only one message per "connection".
|
97
|
+
def stream_eof? stream
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
101
|
+
# Nothing to be closed for Resque.
|
102
|
+
def _server_close_connection! in_stream, out_stream
|
103
|
+
# NOTHING
|
104
|
+
end
|
105
|
+
|
106
|
+
def serve_stream_message! in_stream, out_stream # ignored
|
107
|
+
save = Thread.current[:asir_transport_resque_instance]
|
108
|
+
Thread.current[:asir_transport_resque_instance] = self
|
109
|
+
poll_throttle throttle do
|
110
|
+
# $stderr.puts " #{self} resque_worker = #{resque_worker} on queues #{resque_worker.queues}"
|
111
|
+
if job = resque_worker.reserve
|
112
|
+
$stderr.puts " #{self} serve_stream_message! job=#{job.class}:#{job.inspect}" if @verbose >= 2
|
113
|
+
resque_worker.process(job)
|
114
|
+
end
|
115
|
+
job
|
116
|
+
end
|
117
|
+
self
|
118
|
+
ensure
|
119
|
+
Thread.current[:asir_transport_resque_instance] = save
|
120
|
+
end
|
121
|
+
|
122
|
+
# Class method entry point from Resque::Job.perform.
|
123
|
+
def self.perform payload
|
124
|
+
# $stderr.puts " #{self} process_job payload=#{payload.inspect}"
|
125
|
+
t = Thread.current[:asir_transport_resque_instance]
|
126
|
+
# Pass payload as in_stream; _receive_message will return it.
|
127
|
+
t.serve_message! payload, nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def _receive_message payload, additional_data # is actual payload
|
131
|
+
# $stderr.puts " #{self} _receive_message payload=#{payload.inspect}"
|
132
|
+
[ payload, nil ]
|
133
|
+
end
|
134
|
+
|
135
|
+
####################################
|
136
|
+
|
137
|
+
def resque_uri
|
138
|
+
@resque_uri ||=
|
139
|
+
(
|
140
|
+
unless scheme == 'redis'
|
141
|
+
raise ArgumentError, "Invalid resque URI: #{uri.inspect}"
|
142
|
+
end
|
143
|
+
_uri
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
def resque_connect!
|
148
|
+
@redis =
|
149
|
+
::Redis.new({
|
150
|
+
:host => address || '127.0.0.1',
|
151
|
+
:port => port,
|
152
|
+
:thread_safe => true,
|
153
|
+
})
|
154
|
+
if namespace_
|
155
|
+
::Resque.redis =
|
156
|
+
@redis =
|
157
|
+
::Redis::Namespace.new(namespace_, :redis => @redis)
|
158
|
+
::Resque.redis.namespace = namespace_
|
159
|
+
else
|
160
|
+
::Resque.redis = @redis
|
161
|
+
end
|
162
|
+
# $stderr.puts " *** #{$$} #{self} resque_connect! #{@redis.inspect}"
|
163
|
+
@redis
|
164
|
+
end
|
165
|
+
|
166
|
+
def resque_disconnect!
|
167
|
+
::Resque.redis = nil
|
168
|
+
end
|
169
|
+
|
170
|
+
def resque_worker
|
171
|
+
@resque_worker ||= ::Resque::Worker.new(queues_)
|
172
|
+
end
|
173
|
+
|
174
|
+
def server_on_start!
|
175
|
+
# prune_dead_workers expects processes to have "resque " in the name.
|
176
|
+
@save_progname ||= $0.dup
|
177
|
+
$0 = "resque #{$0}"
|
178
|
+
if worker = resque_worker
|
179
|
+
worker.prune_dead_workers
|
180
|
+
worker.register_worker
|
181
|
+
end
|
182
|
+
self
|
183
|
+
end
|
184
|
+
|
185
|
+
def server_on_stop!
|
186
|
+
$0 = @save_progname if @save_progname
|
187
|
+
if worker = @resque_worker
|
188
|
+
worker.unregister_worker
|
189
|
+
end
|
190
|
+
self
|
191
|
+
end
|
192
|
+
|
193
|
+
#########################################
|
194
|
+
|
195
|
+
def _start_conduit!
|
196
|
+
@redis_dir ||= "/tmp"
|
197
|
+
@redis_conf ||= "#{@redis_dir}/asir-redis-#{port}.conf"
|
198
|
+
@redis_log ||= "#{@redis_dir}/asir-redis-#{port}.log"
|
199
|
+
::File.open(@redis_conf, "w+") do | out |
|
200
|
+
out.puts "daemonize no"
|
201
|
+
out.puts "port #{port}"
|
202
|
+
out.puts "loglevel warning"
|
203
|
+
out.puts "logfile #{@redis_log}"
|
204
|
+
end
|
205
|
+
exec "redis-server", @redis_conf
|
206
|
+
end
|
207
|
+
end
|
208
|
+
# !SLIDE END
|
209
|
+
end # class
|
210
|
+
end # module
|
211
|
+
|
212
|
+
|
@@ -11,19 +11,29 @@ module ASIR
|
|
11
11
|
# Serve all Messages from a stream.
|
12
12
|
def serve_stream! in_stream, out_stream
|
13
13
|
with_server_signals! do
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
@running = true
|
15
|
+
_serve_stream! in_stream, out_stream
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def _serve_stream! in_stream, out_stream
|
20
|
+
while @running && ! stream_eof?(in_stream)
|
21
|
+
begin
|
22
|
+
serve_stream_message! in_stream, out_stream
|
23
|
+
rescue Error::Terminate => err
|
24
|
+
@running = false
|
25
|
+
_log [ :serve_stream_terminate, err ]
|
26
|
+
rescue ::Exception => err
|
27
|
+
_log [ :serve_stream_error, err ]
|
23
28
|
end
|
24
29
|
end
|
25
30
|
end
|
26
31
|
|
32
|
+
# Subclasses can override this method.
|
33
|
+
def stream_eof? stream
|
34
|
+
stream.eof?
|
35
|
+
end
|
36
|
+
|
27
37
|
# !SLIDE
|
28
38
|
# Serve a Message from a stream.
|
29
39
|
def serve_stream_message! in_stream, out_stream
|
@@ -20,10 +20,11 @@ module ASIR
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def _server_accept_connection! server
|
23
|
-
server.accept
|
23
|
+
socket = server.accept
|
24
|
+
[ socket, socket ] # Use same socket for in_stream and out_stream
|
24
25
|
end
|
25
26
|
|
26
|
-
def _server_close_connection! stream
|
27
|
+
def _server_close_connection! stream, out_stream
|
27
28
|
stream.close rescue nil
|
28
29
|
end
|
29
30
|
end
|
data/lib/asir/transport/zmq.rb
CHANGED
@@ -50,7 +50,6 @@ module ASIR
|
|
50
50
|
stream.recv 0
|
51
51
|
end
|
52
52
|
|
53
|
-
# def scheme; SCHEME; end; SCHEME = 'tcp'.freeze
|
54
53
|
def queue
|
55
54
|
@queue ||=
|
56
55
|
(
|
@@ -69,23 +68,21 @@ module ASIR
|
|
69
68
|
(queue.empty? ? queue : queue + " ").freeze
|
70
69
|
end
|
71
70
|
|
71
|
+
# server represents a receiving ZMQ endpoint.
|
72
|
+
def _server_accept_connection! server
|
73
|
+
[ server, @one_way ? nil : server ]
|
74
|
+
end
|
72
75
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
self
|
87
|
-
ensure
|
88
|
-
_server_close!
|
76
|
+
# ZMQ is message-oriented, process only one message per "connection".
|
77
|
+
alias :_server_serve_stream :serve_message!
|
78
|
+
|
79
|
+
def stream_eof? stream
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
# Nothing to be closed for ZMQ.
|
84
|
+
def _server_close_connection! in_stream, out_stream
|
85
|
+
# NOTHING
|
89
86
|
end
|
90
87
|
|
91
88
|
def zmq_uri
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'asir'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module ASIR
|
5
|
+
module UriConfig
|
6
|
+
attr_accessor :uri, :scheme, :host, :port, :path
|
7
|
+
attr_accessor :scheme_default, :host_default, :port_default, :path_default
|
8
|
+
alias :protocol :scheme
|
9
|
+
alias :protocol= :scheme=
|
10
|
+
alias :address :host
|
11
|
+
alias :address= :host=
|
12
|
+
|
13
|
+
def uri
|
14
|
+
@uri ||= "#{scheme}://#{host}:#{port}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def _uri
|
18
|
+
@_uri ||=
|
19
|
+
URI === @uri ? @uri : URI.parse(uri)
|
20
|
+
end
|
21
|
+
|
22
|
+
def scheme
|
23
|
+
@scheme ||=
|
24
|
+
(@uri && _uri.scheme) ||
|
25
|
+
@scheme_default ||
|
26
|
+
S_TCP
|
27
|
+
end
|
28
|
+
S_TCP = 'tcp'.freeze
|
29
|
+
|
30
|
+
def host
|
31
|
+
@host ||=
|
32
|
+
(@uri && _uri.host) ||
|
33
|
+
@host_default ||
|
34
|
+
S_LOCALHOST
|
35
|
+
end
|
36
|
+
S_LOCALHOST = '127.0.0.1'.freeze
|
37
|
+
|
38
|
+
def port
|
39
|
+
@port ||=
|
40
|
+
(@uri && _uri.port) ||
|
41
|
+
@port_default ||
|
42
|
+
(raise Error, "#{self.class}: port not set.")
|
43
|
+
end
|
44
|
+
|
45
|
+
def path
|
46
|
+
@path ||=
|
47
|
+
(@uri && _uri.path) ||
|
48
|
+
@path_default
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/asir/version.rb
CHANGED
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
require 'asir'
|
4
|
+
|
5
|
+
describe "ASIR::Client" do
|
6
|
+
attr_accessor :client, :data, :object
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
self.data = { }
|
10
|
+
transport = ASIR::Transport::Local.new
|
11
|
+
self.object = ASIR::Test::TestObject.new(self)
|
12
|
+
self.client = object.class.asir
|
13
|
+
client.transport = transport
|
14
|
+
client.transport.should == transport
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should return the same Proxy instance for a Module.' do
|
18
|
+
object.class.asir.object_id.should == object.class.asir.object_id
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return a cloned Proxy instances for each object.' do
|
22
|
+
object.asir.object_id.should_not == client.object_id
|
23
|
+
object.asir.object_id.should_not == object.asir.object_id
|
24
|
+
object.asir.transport.object_id.should == client.transport.object_id
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should return a cloned Proxy for class.asir._configure.' do
|
28
|
+
client._configure { | message, proxy | }.object_id.should_not == client.object_id
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should not return a cloned Proxy for object.asir._configure.' do
|
32
|
+
c = object.asir
|
33
|
+
c._configure { | message, proxy | }.object_id.should == c.object_id
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should handle _configure blocks' do
|
37
|
+
proxy = object.asir._configure { | message, proxy |
|
38
|
+
message[:test_proxy] = proxy
|
39
|
+
message[:test_data] = :test
|
40
|
+
}
|
41
|
+
proxy.object_id.should_not == client.object_id
|
42
|
+
proxy.object_id.should_not == object.asir.object_id
|
43
|
+
proxy.return_argument :foo
|
44
|
+
object.message[:test_data].should == :test
|
45
|
+
object.message[:test_proxy].should == proxy
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|