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,36 @@
1
+ require 'securerandom'
2
+
3
+ # Borrowed from Celluloid. Using thread locals instead of extending the base
4
+ # Thread class, though.
5
+ module Agent
6
+ module UUID
7
+ values = SecureRandom.hex(9).match(/(.{8})(.{4})(.{3})(.{3})/)
8
+ PREFIX = "#{values[1]}_#{values[2]}_4#{values[3]}_8#{values[4]}".freeze
9
+ BLOCK_SIZE = 0x10000
10
+
11
+ @counter = 0
12
+ @counter_mutex = Mutex.new
13
+
14
+ def self.generate
15
+ thread = Thread.current
16
+
17
+ unless thread[:__agent_uuid_limit__]
18
+ @counter_mutex.synchronize do
19
+ block_base = @counter
20
+ @counter += BLOCK_SIZE
21
+ thread[:__agent_uuid_counter__] = block_base
22
+ thread[:__agent_uuid_limit__] = @counter - 1
23
+ end
24
+ end
25
+
26
+ counter = thread[:__agent_uuid_counter__]
27
+ if thread[:__agent_uuid_counter__] >= thread[:__agent_uuid_limit__]
28
+ thread[:__agent_uuid_counter__] = thread[:__agent_uuid_limit__] = nil
29
+ else
30
+ thread[:__agent_uuid_counter__] += 1
31
+ end
32
+
33
+ "#{PREFIX}_#{sprintf("%012x", counter)}".freeze
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module Agent
2
- VERSION = "0.1.0"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -0,0 +1,43 @@
1
+ require "agent/errors"
2
+
3
+ module Agent
4
+ class WaitGroup
5
+ attr_reader :count
6
+
7
+ def initialize
8
+ @count = 0
9
+ @mutex = Mutex.new
10
+ @cvar = ConditionVariable.new
11
+ end
12
+
13
+ def wait
14
+ @mutex.synchronize do
15
+ while @count > 0
16
+ @cvar.wait(@mutex)
17
+ end
18
+ end
19
+ end
20
+
21
+ def add(delta)
22
+ @mutex.synchronize do
23
+ modify_count(delta)
24
+ end
25
+ end
26
+
27
+ def done
28
+ @mutex.synchronize do
29
+ modify_count(-1)
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ # Expects to be called inside of the mutex
36
+ def modify_count(delta)
37
+ @count += delta
38
+ raise Errors::NegativeWaitGroupCount if @count < 0
39
+ @cvar.signal if @count == 0
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,122 @@
1
+ require "spec_helper"
2
+
3
+ describe Agent::BlockingOnce do
4
+
5
+ before do
6
+ @blocking_once = Agent::BlockingOnce.new
7
+ end
8
+
9
+ it "should execute the block passed to it" do
10
+ r = []
11
+
12
+ @blocking_once.perform do
13
+ r << 1
14
+ end
15
+
16
+ r.size.should == 1
17
+ r.first.should == 1
18
+ end
19
+
20
+ it "should only execute the first block passed to it" do
21
+ r = []
22
+
23
+ @blocking_once.perform do
24
+ r << 1
25
+ end
26
+
27
+ @blocking_once.perform do
28
+ r << 2
29
+ end
30
+
31
+ r.size.should == 1
32
+ r.first.should == 1
33
+ end
34
+
35
+ it "should return the value returned from the block" do
36
+ value, error = @blocking_once.perform do
37
+ 1
38
+ end
39
+
40
+ value.should == 1
41
+ end
42
+
43
+ it "should return nil for value and an error if it has already been used" do
44
+ value, error = @blocking_once.perform{ 1 }
45
+ value.should == 1
46
+ error.should be_nil
47
+
48
+ value, error = @blocking_once.perform{ 2 }
49
+ value.should be_nil
50
+ error.should_not be_nil
51
+ error.should be_message("already performed")
52
+ end
53
+
54
+ it "should roll back and allow the block to be executed again" do
55
+ s = Time.now.to_f
56
+
57
+ finished_channel = channel!(TrueClass, 2)
58
+
59
+ go! do
60
+ @blocking_once.perform do
61
+ sleep 0.1
62
+ finished_channel.send(true)
63
+ raise Agent::Errors::Rollback
64
+ end
65
+ end
66
+
67
+ sleep 0.1 # make sure the first @blocking_once calls #perform
68
+
69
+ go! do
70
+ @blocking_once.perform do
71
+ sleep 0.1
72
+ finished_channel.send(true)
73
+ end
74
+ end
75
+
76
+ 2.times { finished_channel.receive }
77
+
78
+ finished_channel.close
79
+
80
+ # Three sleeps at 0.1 == 0.3, so if it's less than 0.3...
81
+ (Time.now.to_f - s).should < 0.3
82
+ end
83
+
84
+ it "should have minimal contention between threads when they contend for position" do
85
+ r, s = [], Time.now.to_f
86
+
87
+ # Using condition variables to maximize potential contention
88
+ mutex = Mutex.new
89
+ condition = ConditionVariable.new
90
+
91
+ waiting_channel = channel!(TrueClass, 2)
92
+ finished_channel = channel!(TrueClass, 2)
93
+
94
+ go! do
95
+ mutex.synchronize{ waiting_channel.send(true); condition.wait(mutex) }
96
+ @blocking_once.perform{ sleep 0.1; r << 1 }
97
+ finished_channel.send(true)
98
+ end
99
+
100
+ go! do
101
+ mutex.synchronize{ waiting_channel.send(true); condition.wait(mutex) }
102
+ @blocking_once.perform{ sleep 0.1; r << 1 }
103
+ finished_channel.send(true)
104
+ end
105
+
106
+ # wait for both the goroutines to be waiting
107
+ 2.times{ waiting_channel.receive }
108
+
109
+ mutex.synchronize{ condition.broadcast }
110
+
111
+ # wait for the finished channel to be completed
112
+ 2.times{ finished_channel.receive }
113
+
114
+ r.size.should == 1
115
+ # Onlt the first sleep should be performed, so things should quickly
116
+ (Time.now.to_f - s).should be_within(0.05).of(0.15)
117
+
118
+ waiting_channel.close
119
+ finished_channel.close
120
+ end
121
+
122
+ end
@@ -1,38 +1,17 @@
1
- require "helper"
1
+ require "spec_helper"
2
2
 
3
3
  describe Agent::Channel do
4
- # http://golang.org/doc/go_spec.html#Channel_types
5
4
 
6
- include Agent
7
- let(:c) { Channel.new(:name => "spec", :type => String) }
8
-
9
- it "should have a name" do
10
- lambda { Channel.new(:type => String) }.should raise_error(Channel::NoName)
11
- c.name.should == "spec"
12
- end
13
-
14
- it "should respond to close" do
15
- lambda { c.close }.should_not raise_error
16
- c.closed?.should be_true
17
- end
18
-
19
- it "should respond to closed?" do
20
- c.closed?.should be_false
21
- c.close
22
- c.closed?.should be_true
23
- end
24
-
25
- context "deadlock" do
26
- it "should deadlock on single thread" do
27
- c = Channel.new(:name => "deadlock", :type => String)
28
- lambda { c.receive }.should raise_error
5
+ context "naming" do
6
+ it "should not be required" do
7
+ c = nil
8
+ lambda { c = channel!(String) }.should_not raise_error
29
9
  c.close
30
10
  end
31
11
 
32
- it "should not deadlock with multiple threads" do
33
- c = Channel.new(:name => "deadlock", :type => String)
34
- Thread.new { sleep(0.1); c.push "hi" }
35
- lambda { c.receive }.should_not raise_error
12
+ it "be able to be set" do
13
+ c = channel!(String, :name => "gibberish")
14
+ c.name.should == "gibberish"
36
15
  c.close
37
16
  end
38
17
  end
@@ -43,102 +22,194 @@ describe Agent::Channel do
43
22
  # type. The value of an uninitialized channel is nil.
44
23
 
45
24
  it "should support send only" do
46
- c = Channel.new(:name => "spec", :direction => :send, :type => String, :size => 3)
25
+ c = channel!(String, 3, :direction => :send)
47
26
 
48
27
  lambda { c << "hello" }.should_not raise_error
49
28
  lambda { c.push "hello" }.should_not raise_error
50
29
  lambda { c.send "hello" }.should_not raise_error
51
30
 
52
- lambda { c.pop }.should raise_error Channel::InvalidDirection
53
- lambda { c.receive }.should raise_error Channel::InvalidDirection
31
+ c.direction.should == :send
32
+
33
+ lambda { c.pop }.should raise_error Agent::Errors::InvalidDirection
34
+ lambda { c.receive }.should raise_error Agent::Errors::InvalidDirection
54
35
 
55
36
  c.close
56
37
  end
57
38
 
58
39
  it "should support receive only" do
59
- c = Channel.new(:name => "spec", :direction => :receive, :type => String)
40
+ c = channel!(String, :direction => :receive)
41
+
42
+ lambda { c << "hello" }.should raise_error Agent::Errors::InvalidDirection
43
+ lambda { c.push "hello" }.should raise_error Agent::Errors::InvalidDirection
44
+ lambda { c.send "hello" }.should raise_error Agent::Errors::InvalidDirection
60
45
 
61
- lambda { c << "hello" }.should raise_error Channel::InvalidDirection
62
- lambda { c.push "hello" }.should raise_error Channel::InvalidDirection
63
- lambda { c.send "hello" }.should raise_error Channel::InvalidDirection
46
+ c.direction.should == :receive
64
47
 
65
48
  # timeout blocking receive calls
66
- lambda { Timeout::timeout(0.1) { c.pop } }.should raise_error(Timeout::Error)
67
- lambda { Timeout::timeout(0.1) { c.receive } }.should raise_error(Timeout::Error)
49
+ timed_out = false
50
+ select! do |s|
51
+ s.case(c, :receive){}
52
+ s.timeout(0.1){ timed_out = true }
53
+ end
54
+ timed_out.should == true
55
+ c.close
68
56
  end
69
57
 
70
58
  it "should default to bi-directional communication" do
59
+ c = channel!(String, 1)
71
60
  lambda { c.send "hello" }.should_not raise_error
72
61
  lambda { c.receive }.should_not raise_error
62
+
63
+ c.direction.should == :bidirectional
64
+ end
65
+
66
+ it "should be able to be dup'd as a uni-directional channel" do
67
+ c = channel!(String, 1)
68
+
69
+ send_only = c.as_send_only
70
+ send_only.direction.should == :send
71
+
72
+ receive_only = c.as_receive_only
73
+ receive_only.direction.should == :receive
74
+
75
+ send_only.send("nifty")
76
+ receive_only.receive[0].should == "nifty"
77
+ end
78
+ end
79
+
80
+ context "closing" do
81
+ before do
82
+ @c = channel!(String)
83
+ end
84
+
85
+ it "not raise an error the first time it is called" do
86
+ lambda { @c.close }.should_not raise_error
87
+ @c.closed?.should be_true
88
+ end
89
+
90
+ it "should raise an error the second time it is called" do
91
+ @c.close
92
+ lambda { @c.close }.should raise_error(Agent::Errors::ChannelClosed)
93
+ end
94
+
95
+ it "should respond to closed?" do
96
+ @c.closed?.should be_false
97
+ @c.close
98
+ @c.closed?.should be_true
99
+ end
100
+
101
+ it "should return that a receive was a failure when a channel is closed while being read from" do
102
+ go!{ sleep 0.01; @c.close }
103
+ _, ok = @c.receive
104
+ ok.should be_false
105
+ end
106
+
107
+ it "should raise an error when sending to a channel that has already been closed" do
108
+ @c.close
109
+ lambda { @c.send("a") }.should raise_error(Agent::Errors::ChannelClosed)
110
+ end
111
+
112
+ it "should raise an error when receiving from a channel that has already been closed" do
113
+ @c.close
114
+ lambda { @c.receive }.should raise_error(Agent::Errors::ChannelClosed)
115
+ end
116
+ end
117
+
118
+ context "deadlock" do
119
+ before do
120
+ @c = channel!(String)
121
+ end
122
+
123
+ it "should deadlock on single thread", :vm => :ruby do
124
+ lambda { @c.receive }.should raise_error
125
+ end
126
+
127
+ it "should not deadlock with multiple threads" do
128
+ go!{ sleep(0.1); @c.push "hi" }
129
+ lambda { @c.receive }.should_not raise_error
73
130
  end
74
131
  end
75
132
 
76
133
  context "typed" do
77
134
  it "should create a typed channel" do
78
- lambda { Channel.new(:name => "spec") }.should raise_error Channel::Untyped
79
- lambda { Channel.new(:name => "spec", :type => Integer) }.should_not raise_error
135
+ lambda { channel! }.should raise_error Agent::Errors::Untyped
136
+ c = nil
137
+ lambda { c = channel!(Integer) }.should_not raise_error
138
+ c.close
80
139
  end
81
140
 
82
141
  it "should reject messages of invalid type" do
83
- lambda { c.send 1 }.should raise_error(Channel::InvalidType)
142
+ c = channel!(String)
143
+ go!{ c.receive }
144
+ lambda { c.send 1 }.should raise_error(Agent::Errors::InvalidType)
84
145
  lambda { c.send "hello" }.should_not raise_error
85
- c.receive
146
+ c.close
86
147
  end
87
148
  end
88
149
 
89
- context "transport" do
90
- it "should default to memory transport" do
91
- c.transport.should == Agent::Transport::Queue
92
- end
93
-
94
- context "channels of channels" do
95
- # One of the most important properties of Go is that a channel is a first-class
96
- # value that can be allocated and passed around like any other. A common use of
97
- # this property is to implement safe, parallel demultiplexing.
98
- # - http://golang.org/doc/effective_go.html#chan_of_chan
150
+ context "buffering" do
151
+ # The capacity, in number of elements, sets the size of the buffer in the channel.
152
+ # If the capacity is greater than zero, the channel is buffered: provided the
153
+ # buffer is not full, sends can succeed without blocking. If the capacity is zero
154
+ # or absent, the communication succeeds only when both a sender and receiver are ready.
99
155
 
100
- it "should be a first class, serializable value" do
101
- lambda { Marshal.dump(c) }.should_not raise_error
102
- lambda { Marshal.load(Marshal.dump(c)).is_a? Channel }.should_not raise_error
103
- end
156
+ it "should default to unbuffered" do
157
+ n = Time.now
158
+ c = channel!(String)
104
159
 
105
- it "should be able to pass as a value on a different channel" do
106
- c.send "hello"
160
+ go!{ sleep(0.15); c.send("hello") }
161
+ c.receive[0].should == "hello"
107
162
 
108
- cm = Marshal.load(Marshal.dump(c))
109
- cm.receive.should == "hello"
110
- end
163
+ (Time.now - n).should be_within(0.05).of(0.15)
111
164
  end
112
165
 
113
- context "capacity" do
114
- # The capacity, in number of elements, sets the size of the buffer in the channel.
115
- # If the capacity is greater than zero, the channel is asynchronous: provided the
116
- # buffer is not full, sends can succeed without blocking. If the capacity is zero
117
- # or absent, the communication succeeds only when both a sender and receiver are ready.
166
+ it "should support buffered" do
167
+ c = channel!(String, 2)
168
+ r = []
118
169
 
119
- it "should default to synchronous communication" do
120
- c = Channel.new(:name => "buffered", :type => String)
170
+ c.send "hello 1"
171
+ c.send "hello 2"
121
172
 
122
- c.send "hello"
123
- c.receive.should == "hello"
124
- lambda { Timeout::timeout(0.1) { c.receive } }.should raise_error(Timeout::Error)
173
+ select! do |s|
174
+ s.case(c, :send, "hello 3")
175
+ s.timeout(0.1)
176
+ end
125
177
 
126
- c.close
178
+ c.receive[0].should == "hello 1"
179
+ c.receive[0].should == "hello 2"
180
+ select! do |s|
181
+ s.case(c, :receive){|v| r.push(v) }
182
+ s.timeout(0.1)
127
183
  end
128
184
 
129
- it "should support asynchronous communication with buffered capacity" do
130
- c = Channel.new(:name => "buffered", :type => String, :size => 2)
185
+ c.close
186
+ end
187
+ end
131
188
 
132
- c.send "hello 1"
133
- c.send "hello 2"
134
- lambda { Timeout::timeout(0.1) { c.send "hello 3" } }.should raise_error(Timeout::Error)
189
+ context "channels of channels" do
190
+ before do
191
+ @c = channel!(String, 1)
192
+ end
193
+
194
+ after do
195
+ @c.close unless @c.closed?
196
+ end
135
197
 
136
- c.receive.should == "hello 1"
137
- c.receive.should == "hello 2"
138
- lambda { Timeout::timeout(0.1) { c.receive } }.should raise_error(Timeout::Error)
198
+ # One of the most important properties of Go is that a channel is a first-class
199
+ # value that can be allocated and passed around like any other. A common use of
200
+ # this property is to implement safe, parallel demultiplexing.
201
+ # - http://golang.org/doc/effective_go.html#chan_of_chan
139
202
 
140
- c.close
141
- end
203
+ it "should be a first class, serializable value" do
204
+ lambda { Marshal.dump(@c) }.should_not raise_error
205
+ lambda { Marshal.load(Marshal.dump(@c)).is_a?(Agent::Channel) }.should_not raise_error
206
+ end
207
+
208
+ it "should be able to pass as a value on a different channel" do
209
+ @c.send "hello"
210
+
211
+ cm = Marshal.load(Marshal.dump(@c))
212
+ cm.receive[0].should == "hello"
142
213
  end
143
214
  end
144
215