agent 0.1.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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