agent 0.1.0 → 0.9.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/.gitignore +2 -0
- data/.rspec +0 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +12 -12
- data/README.md +51 -36
- data/Rakefile +5 -0
- data/agent.gemspec +1 -0
- data/autotest/discover.rb +9 -1
- data/benchmark/multi_ruby_bench.sh +87 -0
- data/benchmark/sieve.rb +238 -0
- data/examples/agent-workers.rb +51 -0
- data/examples/producer-consumer.rb +15 -0
- data/lib/agent.rb +1 -15
- data/lib/agent/all.rb +22 -0
- data/lib/agent/blocking_once.rb +26 -0
- data/lib/agent/channel.rb +88 -41
- data/lib/agent/error.rb +15 -0
- data/lib/agent/errors.rb +14 -0
- data/lib/agent/go.rb +9 -0
- data/lib/agent/kernel/channel.rb +7 -0
- data/lib/agent/kernel/go.rb +7 -0
- data/lib/agent/kernel/select.rb +7 -0
- data/lib/agent/notifier.rb +34 -0
- data/lib/agent/once.rb +32 -0
- data/lib/agent/pop.rb +70 -0
- data/lib/agent/push.rb +70 -0
- data/lib/agent/queue.rb +133 -0
- data/lib/agent/queue/buffered.rb +68 -0
- data/lib/agent/queue/unbuffered.rb +88 -0
- data/lib/agent/queues.rb +50 -0
- data/lib/agent/selector.rb +119 -0
- data/lib/agent/uuid.rb +36 -0
- data/lib/agent/version.rb +1 -1
- data/lib/agent/wait_group.rb +43 -0
- data/spec/blocking_once_spec.rb +122 -0
- data/spec/channel_spec.rb +153 -82
- data/spec/error_spec.rb +15 -0
- data/spec/examples/channel_of_channels_spec.rb +17 -14
- data/spec/examples/producer_consumer_spec.rb +26 -16
- data/spec/examples/sieve_spec.rb +43 -37
- data/spec/go_spec.rb +17 -0
- data/spec/notifier_spec.rb +42 -0
- data/spec/once_spec.rb +91 -0
- data/spec/pop_spec.rb +97 -0
- data/spec/push_spec.rb +95 -0
- data/spec/queue_spec.rb +335 -37
- data/spec/queues_spec.rb +28 -0
- data/spec/selector_spec.rb +398 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/uuid_spec.rb +13 -0
- data/spec/wait_group_spec.rb +45 -0
- metadata +94 -54
- data/lib/agent/transport/queue.rb +0 -82
- data/spec/helper.rb +0 -5
data/spec/queues_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Agent::Queues do
|
4
|
+
after do
|
5
|
+
Agent::Queues.clear
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should register queues" do
|
9
|
+
Agent::Queues.register("foo", String, 10)
|
10
|
+
Agent::Queues["foo"].should be_a(Agent::Queue)
|
11
|
+
Agent::Queues["foo"].type.should == String
|
12
|
+
Agent::Queues["foo"].max.should == 10
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should delete queues" do
|
16
|
+
Agent::Queues.register("foo", String, 10)
|
17
|
+
Agent::Queues.delete("foo")
|
18
|
+
Agent::Queues["foo"].should be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should remove all queues queues" do
|
22
|
+
Agent::Queues.register("foo", String, 10)
|
23
|
+
Agent::Queues.register("bar", String, 10)
|
24
|
+
Agent::Queues.clear
|
25
|
+
Agent::Queues["foo"].should be_nil
|
26
|
+
Agent::Queues["bar"].should be_nil
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,398 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Agent::Selector do
|
4
|
+
# A "select" statement chooses which of a set of possible communications will
|
5
|
+
# proceed. It looks similar to a "switch" statement but with the cases all
|
6
|
+
# referring to communication operations.
|
7
|
+
# - http://golang.org/doc/go_spec.html#Select_statements
|
8
|
+
|
9
|
+
it "should yield Selector on select call" do
|
10
|
+
select! {|s| s.should be_kind_of Agent::Selector}
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return immediately on empty select block" do
|
14
|
+
s = Time.now.to_f
|
15
|
+
select! {}
|
16
|
+
|
17
|
+
(Time.now.to_f - s).should be_within(0.05).of(0)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should timeout select statement" do
|
21
|
+
r, now = [], Time.now.to_f
|
22
|
+
select! do |s|
|
23
|
+
s.timeout(0.1) { r.push :timeout }
|
24
|
+
end
|
25
|
+
|
26
|
+
r.first.should == :timeout
|
27
|
+
(Time.now.to_f - now).should be_within(0.05).of(0.1)
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with unbuffered channels" do
|
31
|
+
before do
|
32
|
+
@c = channel!(Integer)
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
@c.close unless @c.closed?
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should evaluate select statements top to bottom" do
|
40
|
+
select! do |s|
|
41
|
+
s.case(@c, :send, 1) {}
|
42
|
+
s.case(@c, :receive) {}
|
43
|
+
s.default {}
|
44
|
+
s.cases.size.should == 3
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should raise an error when a block is missing on receive" do
|
49
|
+
lambda {
|
50
|
+
select! do |s|
|
51
|
+
s.case(@c, :receive)
|
52
|
+
end
|
53
|
+
}.should raise_error(Agent::Errors::BlockMissing)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not raise an error when a block is missing on send" do
|
57
|
+
lambda {
|
58
|
+
go!{ @c.receive }
|
59
|
+
|
60
|
+
select! do |s|
|
61
|
+
s.case(@c, :send, 1)
|
62
|
+
s.cases.size.should == 0
|
63
|
+
end
|
64
|
+
}.should_not raise_error(Agent::Errors::BlockMissing)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should scan all cases to identify available actions and execute first available one" do
|
68
|
+
r = []
|
69
|
+
go!{ @c.send 1 }
|
70
|
+
|
71
|
+
sleep 0.01 # make sure the goroutine executes, brittle
|
72
|
+
|
73
|
+
select! do |s|
|
74
|
+
s.case(@c, :send, 1) { r.push 1 }
|
75
|
+
s.case(@c, :receive) { r.push 2 }
|
76
|
+
s.case(@c, :receive) { r.push 3 }
|
77
|
+
end
|
78
|
+
|
79
|
+
r.size.should == 1
|
80
|
+
r.first.should == 2
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should evaluate default case immediately if no other cases match" do
|
84
|
+
r = []
|
85
|
+
|
86
|
+
select! do |s|
|
87
|
+
s.case(@c, :send, 1) { r.push 1 }
|
88
|
+
s.default { r.push :default }
|
89
|
+
end
|
90
|
+
|
91
|
+
r.size.should == 1
|
92
|
+
r.first.should == :default
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should raise an error if the channel is closed out from under it" do
|
96
|
+
go!{ sleep 0.25; @c.close }
|
97
|
+
|
98
|
+
lambda {
|
99
|
+
select! do |s|
|
100
|
+
s.case(@c, :send, 1)
|
101
|
+
s.case(@c, :receive){}
|
102
|
+
end
|
103
|
+
}.should raise_error(Agent::Errors::ChannelClosed)
|
104
|
+
end
|
105
|
+
|
106
|
+
context "select immediately available channel" do
|
107
|
+
it "should select read channel" do
|
108
|
+
c = channel!(Integer)
|
109
|
+
go!{ c.send(1) }
|
110
|
+
|
111
|
+
sleep 0.01 # make sure the goroutine executes, brittle
|
112
|
+
|
113
|
+
r = []
|
114
|
+
select! do |s|
|
115
|
+
s.case(c, :send, 1) { r.push :send }
|
116
|
+
s.case(c, :receive) { r.push :receive }
|
117
|
+
s.default { r.push :empty }
|
118
|
+
end
|
119
|
+
|
120
|
+
r.size.should == 1
|
121
|
+
r.first.should == :receive
|
122
|
+
c.close
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should select write channel" do
|
126
|
+
c = channel!(Integer)
|
127
|
+
|
128
|
+
go!{ c.receive }
|
129
|
+
|
130
|
+
sleep 0.01 # make sure the goroutine executes, brittle
|
131
|
+
|
132
|
+
r = []
|
133
|
+
select! do |s|
|
134
|
+
s.case(c, :send, 1) { r.push :send }
|
135
|
+
s.case(c, :receive) { r.push :receive }
|
136
|
+
s.default { r.push :empty }
|
137
|
+
end
|
138
|
+
|
139
|
+
r.size.should == 1
|
140
|
+
r.first.should == :send
|
141
|
+
c.close
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "select busy channel" do
|
146
|
+
it "should select busy read channel" do
|
147
|
+
c = channel!(Integer)
|
148
|
+
r = []
|
149
|
+
|
150
|
+
# brittle.. counting on select to execute within 0.5s
|
151
|
+
now = Time.now.to_f
|
152
|
+
go!{ sleep(0.2); c.send 1 }
|
153
|
+
|
154
|
+
select! do |s|
|
155
|
+
s.case(c, :receive) {|value| r.push value }
|
156
|
+
end
|
157
|
+
|
158
|
+
r.size.should == 1
|
159
|
+
(Time.now.to_f - now).should be_within(0.1).of(0.2)
|
160
|
+
c.close
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should select busy write channel" do
|
164
|
+
c = channel!(Integer)
|
165
|
+
|
166
|
+
# brittle.. counting on select to execute within 0.5s
|
167
|
+
now = Time.now.to_f
|
168
|
+
go!{sleep(0.2); c.receive[0].should == 2 }
|
169
|
+
|
170
|
+
select! do |s|
|
171
|
+
s.case(c, :send, 2) {}
|
172
|
+
end
|
173
|
+
|
174
|
+
(Time.now.to_f - now).should be_within(0.1).of(0.2)
|
175
|
+
c.close
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should select first available channel" do
|
179
|
+
# create a write channel and a read channel
|
180
|
+
cw = channel!(Integer)
|
181
|
+
cr = channel!(Integer)
|
182
|
+
ack = channel!(TrueClass)
|
183
|
+
|
184
|
+
res = []
|
185
|
+
|
186
|
+
now = Time.now.to_f
|
187
|
+
|
188
|
+
# read channel: wait for 0.3s before pushing a message into it
|
189
|
+
go!{ sleep(0.3); cr.send 2 }
|
190
|
+
# write channel: wait for 0.1s before consuming the message
|
191
|
+
go!{ sleep(0.1); res.push cw.receive[0]; ack.send(true) }
|
192
|
+
|
193
|
+
# wait until one of the channels become available
|
194
|
+
# cw should fire first and push '3'
|
195
|
+
select! do |s|
|
196
|
+
s.case(cr, :receive) {|value| res.push value }
|
197
|
+
s.case(cw, :send, 3)
|
198
|
+
end
|
199
|
+
|
200
|
+
ack.receive
|
201
|
+
|
202
|
+
res.size.should == 1
|
203
|
+
res.first.should == 3
|
204
|
+
|
205
|
+
# 0.3s goroutine should eventually fire
|
206
|
+
cr.receive[0].should == 2
|
207
|
+
|
208
|
+
(Time.now.to_f - now).should be_within(0.05).of(0.3)
|
209
|
+
cw.close
|
210
|
+
cr.close
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "with buffered channels" do
|
216
|
+
before do
|
217
|
+
@c = channel!(Integer, 1)
|
218
|
+
end
|
219
|
+
|
220
|
+
after do
|
221
|
+
@c.close unless @c.closed?
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should evaluate select statements top to bottom" do
|
225
|
+
select! do |s|
|
226
|
+
s.case(@c, :send, 1) {}
|
227
|
+
s.case(@c, :receive) {}
|
228
|
+
s.cases.size.should == 2
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should raise an error when a block is missing on receive" do
|
233
|
+
lambda {
|
234
|
+
select! do |s|
|
235
|
+
s.case(@c, :receive)
|
236
|
+
s.cases.size.should == 0
|
237
|
+
end
|
238
|
+
}.should raise_error(Agent::Errors::BlockMissing)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should not raise an error when a block is missing on send" do
|
242
|
+
lambda {
|
243
|
+
select! do |s|
|
244
|
+
s.case(@c, :send, 1)
|
245
|
+
s.cases.size.should == 0
|
246
|
+
end
|
247
|
+
}.should_not raise_error(Agent::Errors::BlockMissing)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should scan all cases to identify available actions and execute first available one" do
|
251
|
+
r = []
|
252
|
+
@c.send 1
|
253
|
+
|
254
|
+
select! do |s|
|
255
|
+
s.case(@c, :send, 1) { r.push 1 }
|
256
|
+
s.case(@c, :receive) { r.push 2 }
|
257
|
+
s.case(@c, :receive) { r.push 3 }
|
258
|
+
end
|
259
|
+
|
260
|
+
r.size.should == 1
|
261
|
+
r.first.should == 2
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should evaluate default case immediately if no other cases match" do
|
265
|
+
r = []
|
266
|
+
|
267
|
+
@c.send(1)
|
268
|
+
|
269
|
+
select! do |s|
|
270
|
+
s.case(@c, :send, 1) { r.push 1 }
|
271
|
+
s.default { r.push :default }
|
272
|
+
end
|
273
|
+
|
274
|
+
r.size.should == 1
|
275
|
+
r.first.should == :default
|
276
|
+
end
|
277
|
+
|
278
|
+
it "should raise an error if the channel is closed out from under it" do
|
279
|
+
@c.send(1)
|
280
|
+
|
281
|
+
go!{ sleep 0.25; @c.close }
|
282
|
+
|
283
|
+
lambda {
|
284
|
+
select! do |s|
|
285
|
+
s.case(@c, :send, 1)
|
286
|
+
s.case(@c, :send, 2){}
|
287
|
+
end
|
288
|
+
}.should raise_error(Agent::Errors::ChannelClosed)
|
289
|
+
end
|
290
|
+
|
291
|
+
context "select immediately available channel" do
|
292
|
+
it "should select read channel" do
|
293
|
+
c = channel!(Integer, 1)
|
294
|
+
c.send(1)
|
295
|
+
|
296
|
+
r = []
|
297
|
+
select! do |s|
|
298
|
+
s.case(c, :send, 1) { r.push :send }
|
299
|
+
s.case(c, :receive) { r.push :receive }
|
300
|
+
s.default { r.push :empty }
|
301
|
+
end
|
302
|
+
|
303
|
+
r.size.should == 1
|
304
|
+
r.first.should == :receive
|
305
|
+
c.close
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should select write channel" do
|
309
|
+
c = channel!(Integer, 1)
|
310
|
+
|
311
|
+
r = []
|
312
|
+
select! do |s|
|
313
|
+
s.case(c, :send, 1) { r.push :send }
|
314
|
+
s.case(c, :receive) { r.push :receive }
|
315
|
+
s.default { r.push :empty }
|
316
|
+
end
|
317
|
+
|
318
|
+
r.size.should == 1
|
319
|
+
r.first.should == :send
|
320
|
+
c.close
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
context "select busy channel" do
|
325
|
+
it "should select busy read channel" do
|
326
|
+
c = channel!(Integer, 1)
|
327
|
+
r = []
|
328
|
+
|
329
|
+
# brittle.. counting on select to execute within 0.5s
|
330
|
+
now = Time.now.to_f
|
331
|
+
go!{ sleep(0.2); c.send 1 }
|
332
|
+
|
333
|
+
select! do |s|
|
334
|
+
s.case(c, :receive) {|value| r.push value }
|
335
|
+
end
|
336
|
+
|
337
|
+
r.size.should == 1
|
338
|
+
(Time.now.to_f - now).should be_within(0.1).of(0.2)
|
339
|
+
c.close
|
340
|
+
end
|
341
|
+
|
342
|
+
it "should select busy write channel" do
|
343
|
+
c = channel!(Integer, 1)
|
344
|
+
c.send 1
|
345
|
+
|
346
|
+
# brittle.. counting on select to execute within 0.5s
|
347
|
+
now = Time.now.to_f
|
348
|
+
go!{sleep(0.2); c.receive }
|
349
|
+
|
350
|
+
select! do |s|
|
351
|
+
s.case(c, :send, 2) {}
|
352
|
+
end
|
353
|
+
|
354
|
+
c.receive[0].should == 2
|
355
|
+
(Time.now.to_f - now).should be_within(0.1).of(0.2)
|
356
|
+
c.close
|
357
|
+
end
|
358
|
+
|
359
|
+
it "should select first available channel" do
|
360
|
+
# create a "full" write channel, and "empty" read channel
|
361
|
+
cw = channel!(Integer, 1)
|
362
|
+
cr = channel!(Integer, 1)
|
363
|
+
ack = channel!(TrueClass)
|
364
|
+
|
365
|
+
cw.send(1)
|
366
|
+
|
367
|
+
res = []
|
368
|
+
|
369
|
+
now = Time.now.to_f
|
370
|
+
# empty read channel: wait for 0.5s before pushing a message into it
|
371
|
+
go!{ sleep(0.5); cr.send(2) }
|
372
|
+
# full write channel: wait for 0.2s before consuming the message
|
373
|
+
go!{ sleep(0.2); res.push cw.receive[0]; ack.send(true) }
|
374
|
+
|
375
|
+
# wait until one of the channels become available
|
376
|
+
# cw should fire first and push '3'
|
377
|
+
select! do |s|
|
378
|
+
s.case(cr, :receive) {|value| res.push value }
|
379
|
+
s.case(cw, :send, 3)
|
380
|
+
end
|
381
|
+
|
382
|
+
ack.receive
|
383
|
+
|
384
|
+
# 0.8s goroutine should have consumed the message first
|
385
|
+
res.size.should == 1
|
386
|
+
res.first.should == 1
|
387
|
+
|
388
|
+
# send case should have fired, and we should have a message
|
389
|
+
cw.receive[0].should == 3
|
390
|
+
|
391
|
+
(Time.now.to_f - now).should be_within(0.1).of(0.2)
|
392
|
+
cw.close
|
393
|
+
cr.close
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require "agent"
|
9
|
+
require "timeout"
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
config.filter_run :focus
|
15
|
+
|
16
|
+
config.filter_run_excluding :vm => lambda { |version|
|
17
|
+
!(Config::CONFIG['ruby_install_name'] =~ /^#{version.to_s}/)
|
18
|
+
}
|
19
|
+
end
|
data/spec/uuid_spec.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Agent::UUID do
|
4
|
+
it "should generate a uuid" do
|
5
|
+
Agent::UUID.generate.should match(/^[0-9a-f]{8}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{4}_[0-9a-f]{12}$/)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should generate unique IDs across the BLOCK_SIZE boundary" do
|
9
|
+
upper_bound = Agent::UUID::BLOCK_SIZE * 2 + 10
|
10
|
+
uuids = (1..upper_bound).map{ Agent::UUID.generate }
|
11
|
+
uuids.size.should == uuids.uniq.size
|
12
|
+
end
|
13
|
+
end
|