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,97 @@
1
+ require "spec_helper"
2
+
3
+ describe Agent::Pop do
4
+
5
+ context "in its basic operation" do
6
+ before do
7
+ @pop = Agent::Pop.new
8
+ @ack = channel!(Time)
9
+ end
10
+
11
+ it "should close" do
12
+ @pop.should_not be_closed
13
+ @pop.close
14
+ @pop.should be_closed
15
+ end
16
+
17
+ it "should run multiple times" do
18
+ @pop.send{Marshal.dump(1)}
19
+ @pop.should be_received
20
+ @pop.send{Marshal.dump(2)}
21
+ @pop.object.should == 2
22
+ end
23
+
24
+ it "should continue when received" do
25
+ go!{ @pop.wait; @ack.send(Time.now) }
26
+ sleep 0.2
27
+ @pop.send{Marshal.dump(1)}
28
+
29
+ s, _ = @ack.receive
30
+
31
+ (Time.now - s).should be_within(0.01).of(0)
32
+ end
33
+
34
+ it "should continue when closed" do
35
+ go!{ @pop.wait; @ack.send(Time.now) }
36
+ sleep 0.2
37
+ @pop.close
38
+
39
+ s, _ = @ack.receive
40
+
41
+ (Time.now - s).should be_within(0.01).of(0)
42
+ end
43
+
44
+ it "be able to be gracefully rolled back" do
45
+ @pop.should_not be_received
46
+ @pop.send{ raise Agent::Errors::Rollback }
47
+ @pop.should_not be_received
48
+ end
49
+ end
50
+
51
+ context "with a blocking_once" do
52
+ before do
53
+ @blocking_once = Agent::BlockingOnce.new
54
+ @pop = Agent::Pop.new(:blocking_once => @blocking_once)
55
+ end
56
+
57
+ it "should only send only once" do
58
+ @blocking_once.should_not be_performed
59
+ @pop.send{Marshal.dump(1)}
60
+ @pop.should be_received
61
+ @blocking_once.should be_performed
62
+
63
+ @pop.send{Marshal.dump(2)}
64
+ @pop.object.should == 1
65
+
66
+ lambda{@pop.send{raise "an error"} }.should_not raise_error
67
+ end
68
+
69
+ it "be able to be gracefully rolled back" do
70
+ @blocking_once.should_not be_performed
71
+ @pop.should_not be_received
72
+ @pop.send{ raise Agent::Errors::Rollback }
73
+ @blocking_once.should_not be_performed
74
+ @pop.should_not be_received
75
+ end
76
+ end
77
+
78
+ context "with a notifier" do
79
+ before do
80
+ @notifier = Agent::Notifier.new
81
+ @pop = Agent::Pop.new(:notifier => @notifier)
82
+ end
83
+
84
+ it "should notify when being sent" do
85
+ @notifier.should_not be_notified
86
+ @pop.send{Marshal.dump(1)}
87
+ @notifier.should be_notified
88
+ end
89
+
90
+ it "should notify when being closed" do
91
+ @notifier.should_not be_notified
92
+ @pop.close
93
+ @notifier.should be_notified
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,95 @@
1
+ require "spec_helper"
2
+
3
+ describe Agent::Push do
4
+
5
+ context "in its basic operation" do
6
+ before do
7
+ @push = Agent::Push.new("1")
8
+ @ack = channel!(Time)
9
+ end
10
+
11
+ it "should close" do
12
+ @push.should_not be_closed
13
+ @push.close
14
+ @push.should be_closed
15
+ end
16
+
17
+ it "should run multiple times" do
18
+ i = 0
19
+ @push.receive{|v| i += 1 }
20
+ @push.should be_sent
21
+ @push.receive{|v| i += 1 }
22
+ i.should == 2
23
+ end
24
+
25
+ it "should continue when sent" do
26
+ go!{ @push.wait; @ack.send(Time.now) }
27
+ sleep 0.2
28
+ @push.receive{|v|}
29
+
30
+ s, _ = @ack.receive
31
+
32
+ (Time.now - s).should be_within(0.01).of(0)
33
+ end
34
+
35
+ it "should raise an error on the waiter when closed" do
36
+ go!{ sleep 0.1; @push.close }
37
+ lambda{ @push.wait }.should raise_error(Agent::Errors::ChannelClosed)
38
+ end
39
+
40
+ it "be able to be gracefully rolled back" do
41
+ @push.should_not be_sent
42
+ @push.receive{|v| raise Agent::Errors::Rollback }
43
+ @push.should_not be_sent
44
+ end
45
+ end
46
+
47
+ context "with a blocking_once" do
48
+ before do
49
+ @blocking_once = Agent::BlockingOnce.new
50
+ @push = Agent::Push.new("1", :blocking_once => @blocking_once)
51
+ end
52
+
53
+ it "should only send only once" do
54
+ i = 0
55
+
56
+ @blocking_once.should_not be_performed
57
+ @push.receive{|v| i += 1 }
58
+ @push.should be_sent
59
+ @blocking_once.should be_performed
60
+
61
+ @push.receive{|v| i += 1 }
62
+ i.should == 1
63
+
64
+ lambda{@push.receive{raise "an error"} }.should_not raise_error
65
+ end
66
+
67
+ it "be able to be gracefully rolled back" do
68
+ @blocking_once.should_not be_performed
69
+ @push.should_not be_sent
70
+ @push.receive{|v| raise Agent::Errors::Rollback }
71
+ @blocking_once.should_not be_performed
72
+ @push.should_not be_sent
73
+ end
74
+ end
75
+
76
+ context "with a notifier" do
77
+ before do
78
+ @notifier = Agent::Notifier.new
79
+ @push = Agent::Push.new("1", :notifier => @notifier)
80
+ end
81
+
82
+ it "should notify when being sent" do
83
+ @notifier.should_not be_notified
84
+ @push.receive{|v|}
85
+ @notifier.should be_notified
86
+ end
87
+
88
+ it "should notify when being closed" do
89
+ @notifier.should_not be_notified
90
+ @push.close
91
+ @notifier.should be_notified
92
+ end
93
+ end
94
+
95
+ end
@@ -1,53 +1,351 @@
1
- require 'helper'
1
+ require "spec_helper"
2
2
 
3
- describe Agent::Transport::Queue do
4
- include Agent::Transport
3
+ describe Agent::Queue do
5
4
 
6
- it "should support synchronous, unbuffered communication" do
7
- lambda { Queue.new("spec") }.should_not raise_error
5
+ context "with an buffered queue" do
6
+ before do
7
+ @queue = Agent::Queue::Buffered.new(String, 2)
8
+ end
8
9
 
9
- q = Queue.new("spec")
10
- q.max.should == 1
11
- q.async?.should be_false
10
+ it "should be buffered" do
11
+ @queue.should be_buffered
12
+ end
12
13
 
13
- lambda { q.send("hello") }.should_not raise_error
14
- lambda { q.send("hello", true) }.should raise_error(ThreadError, "buffer full")
14
+ it "should not be unbuffered" do
15
+ @queue.should_not be_unbuffered
16
+ end
15
17
 
16
- q.receive.should == "hello"
17
- lambda { q.receive(true) }.should raise_error(ThreadError, "buffer empty")
18
- end
18
+ it "should raise an error if the queue size is <= 0" do
19
+ lambda{ Agent::Queue::Buffered.new(String, 0) }.should raise_error(Agent::Errors::InvalidQueueSize)
20
+ lambda{ Agent::Queue::Buffered.new(String, -1) }.should raise_error(Agent::Errors::InvalidQueueSize)
21
+ end
19
22
 
20
- it "should support asynchronous, buffered communication" do
21
- lambda { Queue.new("spec", 2) }.should_not raise_error
23
+ it "should raise an erro when an object of an invalid type is pushed" do
24
+ lambda { @queue.push(1) }.should raise_error(Agent::Errors::InvalidType)
25
+ end
22
26
 
23
- q = Queue.new("spec", 2)
24
- q.max.should == 2
25
- q.async?.should be_true
27
+ it "should enqueue and dequeue in order" do
28
+ 20.times{|i| @queue.push(i.to_s, :deferred => true) }
26
29
 
27
- lambda { q.send("hello 1") }.should_not raise_error
28
- lambda { q.send("hello 2", true) }.should_not raise_error(ThreadError, "buffer full")
29
- lambda { q.send("hello 3", true) }.should raise_error(ThreadError, "buffer full")
30
+ previous = -1
30
31
 
31
- q.receive.should == "hello 1"
32
- q.receive.should == "hello 2"
33
- lambda { q.receive(true) }.should raise_error(ThreadError, "buffer empty")
34
- end
32
+ 20.times do |i|
33
+ o = @queue.pop[0].to_i
34
+ o.should > previous
35
+ previous = o
36
+ end
37
+ end
38
+
39
+ context "when the queue is empty" do
40
+ it "should hold any attempts to pop from it" do
41
+ @queue.operations.should be_empty
42
+ @queue.pop(:deferred => true)
43
+ @queue.operations.should_not be_empty
44
+ end
45
+
46
+ it "should be able to be pushed to" do
47
+ @queue.push("1")
48
+ end
49
+
50
+ it "should increase in size when pushed to" do
51
+ @queue.size.should == 0
52
+ @queue.push("1")
53
+ @queue.size.should == 1
54
+ end
55
+
56
+ it "should be pushable" do
57
+ @queue.push?.should == true
58
+ end
59
+
60
+ it "should not be poppable" do
61
+ @queue.pop?.should == false
62
+ end
63
+ end
64
+
65
+ context "when there are elements in the queue and still space left" do
66
+ before do
67
+ @queue.push("1")
68
+ end
69
+
70
+ it "should be able to be pushed to" do
71
+ @queue.push("1")
72
+ end
73
+
74
+ it "should increase in size when pushed to" do
75
+ @queue.size.should == 1
76
+ @queue.push("1")
77
+ @queue.size.should == 2
78
+ end
79
+
80
+ it "should be able to be popped from" do
81
+ @queue.pop[0].should == "1"
82
+ end
83
+
84
+ it "should decrease in size when popped from" do
85
+ @queue.size.should == 1
86
+ @queue.pop
87
+ @queue.size.should == 0
88
+ end
89
+
90
+ it "should be pushable" do
91
+ @queue.push?.should == true
92
+ end
93
+
94
+ it "should be poppable" do
95
+ @queue.pop?.should == true
96
+ end
97
+ end
35
98
 
36
- it "should persist data between queue objects" do
37
- q = Queue.new("spec")
38
- q.send "hello"
99
+ context "when the queue is full" do
100
+ before do
101
+ 2.times { @queue.push("1") }
102
+ end
39
103
 
40
- q = Queue.new("spec")
41
- q.receive.should == "hello"
104
+ it "should hold any attempts to push to it" do
105
+ @queue.operations.should be_empty
106
+ @queue.push("1", :deferred => true)
107
+ @queue.operations.should_not be_empty
108
+ end
109
+
110
+ it "should be able to be popped from" do
111
+ @queue.pop[0].should == "1"
112
+ end
113
+
114
+ it "should not be pushable" do
115
+ @queue.push?.should == false
116
+ end
117
+
118
+ it "should be poppable" do
119
+ @queue.pop?.should == true
120
+ end
121
+ end
122
+
123
+ context "when being closed" do
124
+ before do
125
+ @push1, @push2, @push3 = (1..3).map{ @queue.push("1", :deferred => true) }
126
+ end
127
+
128
+ it "should go from open to closed" do
129
+ @queue.should_not be_closed
130
+ @queue.should be_open
131
+ @queue.close
132
+ @queue.should be_closed
133
+ @queue.should_not be_open
134
+ end
135
+
136
+ it "should close all the waiting operations" do
137
+ @push1.should be_sent
138
+ @push2.should be_sent
139
+ @push3.should_not be_sent
140
+ @push3.should_not be_closed
141
+
142
+ @queue.close
143
+
144
+ @push3.should be_closed
145
+ end
146
+
147
+ it "should clear all waiting operations" do
148
+ @queue.operations.size.should == 1
149
+ @queue.pushes.size.should == 1
150
+ @queue.close
151
+ @queue.operations.size.should == 0
152
+ @queue.pushes.size.should == 0
153
+ end
154
+
155
+ it "should clear all elements at rest" do
156
+ @queue.queue.size.should == 2
157
+ @queue.close
158
+ @queue.queue.size.should == 0
159
+ end
160
+
161
+ it "should raise an error when being acted upon afterwards" do
162
+ @queue.close
163
+ lambda{ @queue.close }.should raise_error(Agent::Errors::ChannelClosed)
164
+ lambda{ @queue.push("1") }.should raise_error(Agent::Errors::ChannelClosed)
165
+ lambda{ @queue.pop }.should raise_error(Agent::Errors::ChannelClosed)
166
+ end
167
+ end
168
+
169
+ context "when removing operations" do
170
+ before do
171
+ @pushes = (1..8).map{|i| @queue.push(i.to_s, :deferred => true) }
172
+ end
173
+
174
+ it "should remove the operations" do
175
+ removable_pushes = @pushes.values_at(5, 6) # values "6" and "7"
176
+ @queue.remove_operations(removable_pushes)
177
+ while @queue.pop?
178
+ i = @queue.pop[0]
179
+ i.should_not be_nil
180
+ i.should_not == "6"
181
+ i.should_not == "7"
182
+ end
183
+ end
184
+ end
42
185
  end
43
186
 
44
- it "should clear registry on close" do
45
- q = Queue.new("spec")
46
- q.send "hello"
47
- q.close
187
+ context "with a unbuffered queue" do
188
+ before do
189
+ @queue = Agent::Queue::Unbuffered.new(String)
190
+ end
191
+
192
+ it "should not be buffered" do
193
+ @queue.should_not be_buffered
194
+ end
48
195
 
49
- q = Queue.new("spec")
50
- lambda { q.receive(true) }.should raise_error(ThreadError, "buffer empty")
51
- end
196
+ it "should be unbuffered" do
197
+ @queue.should be_unbuffered
198
+ end
199
+
200
+ it "should enqueue and dequeue in order" do
201
+ 20.times{|i| @queue.push(i.to_s, :deferred => true) }
202
+
203
+ previous = -1
204
+
205
+ 20.times do |i|
206
+ o = @queue.pop[0].to_i
207
+ o.should > previous
208
+ previous = o
209
+ end
210
+ end
211
+
212
+ context "when there are no operations waiting" do
213
+ it "should not be poppable" do
214
+ @queue.pop?.should == false
215
+ end
216
+
217
+ it "should not be pushable" do
218
+ @queue.push?.should == false
219
+ end
220
+
221
+ it "should queue pushes" do
222
+ @queue.operations.size.should == 0
223
+ push = @queue.push("1", :deferred => true)
224
+ push.should_not be_sent
225
+ @queue.operations.size.should == 1
226
+ end
227
+
228
+ it "should queue pops" do
229
+ @queue.operations.size.should == 0
230
+ pop = @queue.pop(:deferred => true)
231
+ pop.should_not be_received
232
+ @queue.operations.size.should == 1
233
+ end
234
+ end
235
+
236
+ context "when there is a pop waiting" do
237
+ before do
238
+ @pop = @queue.pop(:deferred => true)
239
+ end
240
+
241
+ it "should not be poppable" do
242
+ @queue.pop?.should == false
243
+ end
244
+
245
+ it "should be pushable" do
246
+ @queue.push?.should == true
247
+ end
248
+
249
+ it "should execute a push and the waiting pop immediately" do
250
+ push = @queue.push("1", :deferred => true)
251
+ @pop.should be_received
252
+ push.should be_sent
253
+ @pop.object.should == "1"
254
+ end
255
+
256
+ it "should queue pops" do
257
+ @queue.operations.size.should == 1
258
+ pop = @queue.pop(:deferred => true)
259
+ pop.should_not be_received
260
+ @queue.operations.size.should == 2
261
+ end
262
+ end
263
+
264
+ context "when there is a push waiting" do
265
+ before do
266
+ @push = @queue.push("1", :deferred => true)
267
+ end
268
+
269
+ it "should be poppable" do
270
+ @queue.pop?.should == true
271
+ end
272
+
273
+ it "should not be pushable" do
274
+ @queue.push?.should == false
275
+ end
276
+
277
+ it "should queue pushes" do
278
+ @queue.operations.size.should == 1
279
+ push = @queue.push("1", :deferred => true)
280
+ push.should_not be_sent
281
+ @queue.operations.size.should == 2
282
+ end
283
+
284
+ it "should execute a pop and the waiting push immediately" do
285
+ pop = @queue.pop(:deferred => true)
286
+ @push.should be_sent
287
+ pop.should be_received
288
+ pop.object.should == "1"
289
+ end
290
+ end
291
+
292
+ context "when being closed" do
293
+ before do
294
+ @push1, @push2 = (1..2).map{ @queue.push("1", :deferred => true) }
295
+ end
296
+
297
+ it "should go from open to closed" do
298
+ @queue.should_not be_closed
299
+ @queue.should be_open
300
+ @queue.close
301
+ @queue.should be_closed
302
+ @queue.should_not be_open
303
+ end
304
+
305
+ it "should close all the waiting operations" do
306
+ @push1.should_not be_sent
307
+ @push1.should_not be_closed
308
+ @push2.should_not be_sent
309
+ @push2.should_not be_closed
310
+
311
+ @queue.close
312
+
313
+ @push1.should be_closed
314
+ @push2.should be_closed
315
+ end
316
+
317
+ it "should clear all waiting operations" do
318
+ @queue.operations.size.should == 2
319
+ @queue.pushes.size.should == 2
320
+ @queue.close
321
+ @queue.operations.size.should == 0
322
+ @queue.pushes.size.should == 0
323
+ end
324
+
325
+ it "should raise an error when being acted upon afterwards" do
326
+ @queue.close
327
+ lambda{ @queue.close }.should raise_error(Agent::Errors::ChannelClosed)
328
+ lambda{ @queue.push("1") }.should raise_error(Agent::Errors::ChannelClosed)
329
+ lambda{ @queue.pop }.should raise_error(Agent::Errors::ChannelClosed)
330
+ end
331
+ end
332
+
333
+ context "when removing operations" do
334
+ before do
335
+ @pushes = (1..8).map{|i| @queue.push(i.to_s, :deferred => true) }
336
+ end
337
+
338
+ it "should remove the operations" do
339
+ removable_pushes = @pushes.values_at(5, 6) # values "6" and "7"
340
+ @queue.remove_operations(removable_pushes)
341
+ while @queue.pop?
342
+ i = @queue.pop[0]
343
+ i.should_not be_nil
344
+ i.should_not == "6"
345
+ i.should_not == "7"
346
+ end
347
+ end
348
+ end
349
+ end
52
350
 
53
351
  end