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.
Files changed (54) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +0 -0
  3. data/Gemfile +0 -1
  4. data/Gemfile.lock +12 -12
  5. data/README.md +51 -36
  6. data/Rakefile +5 -0
  7. data/agent.gemspec +1 -0
  8. data/autotest/discover.rb +9 -1
  9. data/benchmark/multi_ruby_bench.sh +87 -0
  10. data/benchmark/sieve.rb +238 -0
  11. data/examples/agent-workers.rb +51 -0
  12. data/examples/producer-consumer.rb +15 -0
  13. data/lib/agent.rb +1 -15
  14. data/lib/agent/all.rb +22 -0
  15. data/lib/agent/blocking_once.rb +26 -0
  16. data/lib/agent/channel.rb +88 -41
  17. data/lib/agent/error.rb +15 -0
  18. data/lib/agent/errors.rb +14 -0
  19. data/lib/agent/go.rb +9 -0
  20. data/lib/agent/kernel/channel.rb +7 -0
  21. data/lib/agent/kernel/go.rb +7 -0
  22. data/lib/agent/kernel/select.rb +7 -0
  23. data/lib/agent/notifier.rb +34 -0
  24. data/lib/agent/once.rb +32 -0
  25. data/lib/agent/pop.rb +70 -0
  26. data/lib/agent/push.rb +70 -0
  27. data/lib/agent/queue.rb +133 -0
  28. data/lib/agent/queue/buffered.rb +68 -0
  29. data/lib/agent/queue/unbuffered.rb +88 -0
  30. data/lib/agent/queues.rb +50 -0
  31. data/lib/agent/selector.rb +119 -0
  32. data/lib/agent/uuid.rb +36 -0
  33. data/lib/agent/version.rb +1 -1
  34. data/lib/agent/wait_group.rb +43 -0
  35. data/spec/blocking_once_spec.rb +122 -0
  36. data/spec/channel_spec.rb +153 -82
  37. data/spec/error_spec.rb +15 -0
  38. data/spec/examples/channel_of_channels_spec.rb +17 -14
  39. data/spec/examples/producer_consumer_spec.rb +26 -16
  40. data/spec/examples/sieve_spec.rb +43 -37
  41. data/spec/go_spec.rb +17 -0
  42. data/spec/notifier_spec.rb +42 -0
  43. data/spec/once_spec.rb +91 -0
  44. data/spec/pop_spec.rb +97 -0
  45. data/spec/push_spec.rb +95 -0
  46. data/spec/queue_spec.rb +335 -37
  47. data/spec/queues_spec.rb +28 -0
  48. data/spec/selector_spec.rb +398 -0
  49. data/spec/spec_helper.rb +19 -0
  50. data/spec/uuid_spec.rb +13 -0
  51. data/spec/wait_group_spec.rb +45 -0
  52. metadata +94 -54
  53. data/lib/agent/transport/queue.rb +0 -82
  54. data/spec/helper.rb +0 -5
@@ -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
@@ -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
@@ -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