jamesgolick-ASS 0.1.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.
- data/ASS.gemspec +62 -0
- data/LICENSE +20 -0
- data/README.textile +162 -0
- data/Rakefile +57 -0
- data/VERSION.yml +4 -0
- data/lib/ass.rb +107 -0
- data/lib/ass/actor.rb +50 -0
- data/lib/ass/amqp.rb +19 -0
- data/lib/ass/callback_factory.rb +95 -0
- data/lib/ass/client.rb +19 -0
- data/lib/ass/peeper.rb +38 -0
- data/lib/ass/rpc.rb +180 -0
- data/lib/ass/server.rb +171 -0
- data/lib/ass/topic.rb +25 -0
- data/spec/actor_spec.rb +84 -0
- data/spec/ass_spec.rb +291 -0
- data/spec/client_spec.rb +50 -0
- data/spec/rpc_spec.rb +74 -0
- data/test/ass_test.rb +7 -0
- data/test/test_helper.rb +10 -0
- metadata +85 -0
data/lib/ass/topic.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# class Topic
|
2
|
+
# def initialize(name,opts={})
|
3
|
+
# @exchange = MQ.topic(name,opts)
|
4
|
+
# end
|
5
|
+
|
6
|
+
# def publish(key,payload,opts={})
|
7
|
+
# @exchange.publish(::Marshal.dump(payload),opts.merge(:routing_key => key))
|
8
|
+
# end
|
9
|
+
|
10
|
+
# def subscribe(matcher,opts={},&block)
|
11
|
+
# ack = opts.delete(:ack)
|
12
|
+
# uid = "#{@exchange.name}.topic.#{rand 999_999_999_999}"
|
13
|
+
# q = MQ.queue(uid,opts)
|
14
|
+
# q.bind(@exchange.name,:key => matcher)
|
15
|
+
# q.subscribe(:ack => ack) { |info,payload|
|
16
|
+
# payload = ::Marshal.load(payload)
|
17
|
+
# if block.arity == 2
|
18
|
+
# block.call(info,payload)
|
19
|
+
# else
|
20
|
+
# block.call(payload)
|
21
|
+
# end
|
22
|
+
# }
|
23
|
+
# q
|
24
|
+
# end
|
25
|
+
# end
|
data/spec/actor_spec.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "lib/ass"
|
3
|
+
require "spec"
|
4
|
+
require 'rant/spec'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
require 'eventmachine'
|
8
|
+
EM.threadpool_size = 1
|
9
|
+
|
10
|
+
describe "Actor" do
|
11
|
+
before do
|
12
|
+
q = Queue.new
|
13
|
+
@server = nil
|
14
|
+
@thread = Thread.new {ASS.start {
|
15
|
+
q << :ready
|
16
|
+
}}
|
17
|
+
@thread.abort_on_exception = true
|
18
|
+
q.pop.should == :ready
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
ASS.stop
|
23
|
+
@thread.join
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should respond by dispatch to methods" do
|
27
|
+
q = Queue.new
|
28
|
+
actor = ASS.actor("spec") {
|
29
|
+
define_method(:foo) do |i|
|
30
|
+
r = [:foo,i]
|
31
|
+
q << r
|
32
|
+
r
|
33
|
+
end
|
34
|
+
|
35
|
+
define_method(:bar) do |i|
|
36
|
+
r = [:bar,i]
|
37
|
+
q << r
|
38
|
+
r
|
39
|
+
end
|
40
|
+
}
|
41
|
+
#a.cast("spec",:foo,1)
|
42
|
+
actor.cast("spec",:foo,1)
|
43
|
+
q.pop.should == [:foo,1]
|
44
|
+
actor.cast("spec",:bar,2)
|
45
|
+
q.pop.should == [:bar,2]
|
46
|
+
|
47
|
+
actor.call("spec",:bar,3) # calling to self
|
48
|
+
q.pop.should == [:bar,3] # input
|
49
|
+
q.pop.should == [:bar,[:bar,3]] # output
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should handle error by calling on_error" do
|
53
|
+
q = Queue.new
|
54
|
+
actor = ASS.actor("spec") {
|
55
|
+
define_method(:on_error) do |e,data|
|
56
|
+
q << [e,data]
|
57
|
+
end
|
58
|
+
}
|
59
|
+
actor.cast("spec",:foo,1)
|
60
|
+
e, data = q.pop
|
61
|
+
e.should be_a(NoMethodError)
|
62
|
+
data.should == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should be able to make service calls" do
|
66
|
+
q = Queue.new
|
67
|
+
a1 = ASS.actor("spec") {
|
68
|
+
define_method(:foo) do |i|
|
69
|
+
q << [:foo,i]
|
70
|
+
cast("spec2",:bar,i+1)
|
71
|
+
end
|
72
|
+
}
|
73
|
+
a2 = ASS.actor("spec2") do
|
74
|
+
define_method(:bar) do |i|
|
75
|
+
q << [:bar,i]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
a1.cast("spec",:foo,1)
|
79
|
+
q.pop.should == [:foo,1]
|
80
|
+
q.pop.should == [:bar,2]
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
end
|
data/spec/ass_spec.rb
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "lib/ass"
|
3
|
+
require "spec"
|
4
|
+
require 'rant/spec'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
require 'eventmachine'
|
8
|
+
EM.threadpool_size = 1
|
9
|
+
|
10
|
+
describe "ASS" do
|
11
|
+
include Rant::Check
|
12
|
+
|
13
|
+
def server(*args,&block)
|
14
|
+
name = args.grep(String).first || "spec"
|
15
|
+
opts = args.grep(Hash).first || {}
|
16
|
+
ASS.server(name,opts) {
|
17
|
+
define_method(:on_call,&block)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(data,opts={},meta=nil)
|
22
|
+
ASS.call("spec",nil,data,{
|
23
|
+
:reply_to => "spec"
|
24
|
+
}.merge(opts),meta)
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast(data,opts={},meta=nil)
|
28
|
+
ASS.cast("spec",nil,data,opts,meta)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "should start and stop" do
|
33
|
+
30.times do
|
34
|
+
begin
|
35
|
+
q = Queue.new
|
36
|
+
s = nil
|
37
|
+
thread = Thread.new {
|
38
|
+
ASS.start {
|
39
|
+
s = server("spec") do |i|
|
40
|
+
q << :ok
|
41
|
+
end
|
42
|
+
q << :ready
|
43
|
+
}
|
44
|
+
q << :done
|
45
|
+
}
|
46
|
+
thread.abort_on_exception = true
|
47
|
+
q.pop.should == :ready
|
48
|
+
cast(0)
|
49
|
+
q.pop.should == :ok
|
50
|
+
ASS.stop
|
51
|
+
q.pop.should == :done
|
52
|
+
rescue => e
|
53
|
+
ASS.stop
|
54
|
+
raise e
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should stop ASS if server does not respond to on_error" do
|
60
|
+
begin
|
61
|
+
old_stderr = $stderr
|
62
|
+
error_output = StringIO.new
|
63
|
+
$stderr = error_output
|
64
|
+
q = Queue.new
|
65
|
+
s = nil
|
66
|
+
t = Thread.new {
|
67
|
+
ASS.start {
|
68
|
+
s = server("spec") do |i|
|
69
|
+
raise "ouch" if i == :die
|
70
|
+
q << :ok
|
71
|
+
i
|
72
|
+
end
|
73
|
+
q << :ready
|
74
|
+
}
|
75
|
+
q << :died
|
76
|
+
}
|
77
|
+
q.pop.should == :ready
|
78
|
+
cast(1)
|
79
|
+
q.pop.should == :ok
|
80
|
+
call(:die)
|
81
|
+
q.pop.should == :died
|
82
|
+
$stderr.string.should_not be_empty
|
83
|
+
EM.reactor_running?.should == false
|
84
|
+
rescue => e
|
85
|
+
ASS.stop
|
86
|
+
raise e
|
87
|
+
ensure
|
88
|
+
$stderr = old_stderr
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
it "should resend a message if server did not ack a message" do
|
94
|
+
begin
|
95
|
+
old_stderr = $stderr
|
96
|
+
error_output = StringIO.new
|
97
|
+
$stderr = error_output
|
98
|
+
q = Queue.new
|
99
|
+
s = nil
|
100
|
+
t1 = Thread.new {
|
101
|
+
ASS.start {
|
102
|
+
s = ASS.server("spec").react(:ack => true) do
|
103
|
+
define_method(:on_call) do |i|
|
104
|
+
raise "ouch" if i == :die
|
105
|
+
q << [header,i]
|
106
|
+
i
|
107
|
+
end
|
108
|
+
end
|
109
|
+
q << :ready
|
110
|
+
}
|
111
|
+
q << :died
|
112
|
+
}
|
113
|
+
q.pop.should == :ready
|
114
|
+
cast(1)
|
115
|
+
header, msg = q.pop
|
116
|
+
msg.should == 1
|
117
|
+
header.redelivered != true
|
118
|
+
cast(:die)
|
119
|
+
q.pop.should == :died
|
120
|
+
t1.join
|
121
|
+
$stderr.string.should_not be_empty
|
122
|
+
EM.reactor_running?.should == false
|
123
|
+
|
124
|
+
# restart server to get have the message resent
|
125
|
+
t2 = Thread.new {
|
126
|
+
ASS.start {
|
127
|
+
s = server { |msg|
|
128
|
+
q << [header,msg]
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
header, msg = q.pop
|
133
|
+
msg.should == :die
|
134
|
+
header.redelivered == true
|
135
|
+
ASS.stop
|
136
|
+
t2.join
|
137
|
+
ensure
|
138
|
+
$stderr = old_stderr
|
139
|
+
ASS.stop
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "server" do
|
144
|
+
before do
|
145
|
+
q = Queue.new
|
146
|
+
@server = nil
|
147
|
+
@thread = Thread.new {ASS.start {
|
148
|
+
q << :ready
|
149
|
+
}}
|
150
|
+
@thread.abort_on_exception = true
|
151
|
+
q.pop.should == :ready
|
152
|
+
end
|
153
|
+
|
154
|
+
after do
|
155
|
+
ASS.stop
|
156
|
+
@thread.join
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should process message with on_call" do
|
160
|
+
input = Queue.new
|
161
|
+
output = Queue.new
|
162
|
+
s = server("spec") do |i|
|
163
|
+
input << i if i == 0
|
164
|
+
output << i if i == 1 # this is the response to self
|
165
|
+
i + 1
|
166
|
+
end
|
167
|
+
# note that when calling self we have the
|
168
|
+
# wierdness of the response sending back to
|
169
|
+
# self.
|
170
|
+
10.times { call(0) }
|
171
|
+
10.times.map { input.pop }.uniq.should == [0]
|
172
|
+
10.times.map { output.pop }.uniq.should == [1]
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should handle error with on_error" do
|
176
|
+
errors = Queue.new
|
177
|
+
msgs = Queue.new
|
178
|
+
s = ASS.server("spec") {
|
179
|
+
def on_call(i)
|
180
|
+
raise "aieee"
|
181
|
+
end
|
182
|
+
|
183
|
+
define_method(:on_error) do |e,msg|
|
184
|
+
msgs << msg
|
185
|
+
errors << e
|
186
|
+
end
|
187
|
+
}
|
188
|
+
10.times { call(0) }
|
189
|
+
10.times {
|
190
|
+
msgs.pop.should == 0
|
191
|
+
#errors.pop.is_a?(Exception).should == true
|
192
|
+
errors.pop.should be_a(RuntimeError)
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should have access to magic service methods" do
|
197
|
+
q = Queue.new
|
198
|
+
s = server { |i|
|
199
|
+
q << [header,method,data,meta]
|
200
|
+
}
|
201
|
+
cast(1,{},:meta)
|
202
|
+
header,method,data,meta = q.pop
|
203
|
+
header.should be_an(MQ::Header)
|
204
|
+
method.should be_nil
|
205
|
+
data.should == 1
|
206
|
+
meta.should == :meta
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
it "should use a new callback instance to process each request" do
|
211
|
+
q = Queue.new
|
212
|
+
s = server { |i|
|
213
|
+
q << self
|
214
|
+
i
|
215
|
+
}
|
216
|
+
2000.times {|i|
|
217
|
+
begin
|
218
|
+
cast(1)
|
219
|
+
rescue => e
|
220
|
+
p "broken at #{i}"
|
221
|
+
raise e
|
222
|
+
end
|
223
|
+
}
|
224
|
+
# we need to hold on to the saved pointers,
|
225
|
+
# otherwise objects would get garbage
|
226
|
+
# collected and their object_ids
|
227
|
+
# reused. This is the case with C-ruby.
|
228
|
+
saved_pointers = []
|
229
|
+
ids = (0...2000).map {
|
230
|
+
o = q.pop
|
231
|
+
saved_pointers << o
|
232
|
+
o.object_id
|
233
|
+
}
|
234
|
+
#pp s.objs
|
235
|
+
ids.uniq.length.should == ids.length
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should receive messages in order from a connection" do
|
239
|
+
q = Queue.new
|
240
|
+
s = server do |i|
|
241
|
+
q << i
|
242
|
+
i
|
243
|
+
end
|
244
|
+
100.times { |i|
|
245
|
+
cast(i)
|
246
|
+
}
|
247
|
+
100.times { |i|
|
248
|
+
q.pop == i
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should resend message" do
|
253
|
+
i = 0
|
254
|
+
q = Queue.new
|
255
|
+
s = server do |data|
|
256
|
+
q << [header,method,data,meta]
|
257
|
+
# requeue the first 100 messages
|
258
|
+
i += 1
|
259
|
+
if i <= 100
|
260
|
+
resend
|
261
|
+
end
|
262
|
+
true
|
263
|
+
end
|
264
|
+
100.times { |i|
|
265
|
+
cast(i,{ :message_id => i }, i)
|
266
|
+
}
|
267
|
+
# the first 100 should be the same message as the messages to 200
|
268
|
+
|
269
|
+
msgs100 = 100.times.map { q.pop }
|
270
|
+
msgs200 = 100.times.map { q.pop }
|
271
|
+
msgs100.zip(msgs200) { |m1,m2|
|
272
|
+
header1,method1,data1,meta1 = m1
|
273
|
+
header2,method2,data2,meta2 = m2
|
274
|
+
|
275
|
+
# everything should be the same except the delivery_tag
|
276
|
+
header2.delivery_tag.should_not == header1.delivery_tag
|
277
|
+
# requeue is different from AMQP's redelivery
|
278
|
+
## it should resend the message as another one.
|
279
|
+
header1.redelivered.should_not == true
|
280
|
+
header2.redelivered.should_not == true
|
281
|
+
|
282
|
+
header2.message_id.should == header2.message_id
|
283
|
+
method2.should == method1
|
284
|
+
data2.should == data1
|
285
|
+
meta1.should == meta2
|
286
|
+
}
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "lib/ass"
|
3
|
+
require "spec"
|
4
|
+
require 'rant/spec'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
require 'eventmachine'
|
8
|
+
EM.threadpool_size = 1
|
9
|
+
|
10
|
+
describe "RPC" do
|
11
|
+
before do
|
12
|
+
q = Queue.new
|
13
|
+
@server = nil
|
14
|
+
@thread = Thread.new {ASS.start(:logging => false) {
|
15
|
+
q << :ready
|
16
|
+
}}
|
17
|
+
@thread.abort_on_exception = true
|
18
|
+
q.pop.should == :ready
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
ASS.stop
|
23
|
+
@thread.join
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should make synchronized rpc call" do
|
27
|
+
ASS.actor("spec") do
|
28
|
+
def foo(i)
|
29
|
+
i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
c = ASS.client
|
33
|
+
c.call("spec",:foo,1).wait.should == 1
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should do cast" do
|
37
|
+
q = Queue.new
|
38
|
+
ASS.actor("spec") do
|
39
|
+
define_method(:foo) do |i|
|
40
|
+
q << i
|
41
|
+
i
|
42
|
+
end
|
43
|
+
end
|
44
|
+
c = ASS.client
|
45
|
+
c.cast("spec",:foo,1)
|
46
|
+
q.pop.should == 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|