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,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