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.
- data/.gitignore +2 -0
- data/.rspec +0 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +12 -12
- data/README.md +51 -36
- data/Rakefile +5 -0
- data/agent.gemspec +1 -0
- data/autotest/discover.rb +9 -1
- data/benchmark/multi_ruby_bench.sh +87 -0
- data/benchmark/sieve.rb +238 -0
- data/examples/agent-workers.rb +51 -0
- data/examples/producer-consumer.rb +15 -0
- data/lib/agent.rb +1 -15
- data/lib/agent/all.rb +22 -0
- data/lib/agent/blocking_once.rb +26 -0
- data/lib/agent/channel.rb +88 -41
- data/lib/agent/error.rb +15 -0
- data/lib/agent/errors.rb +14 -0
- data/lib/agent/go.rb +9 -0
- data/lib/agent/kernel/channel.rb +7 -0
- data/lib/agent/kernel/go.rb +7 -0
- data/lib/agent/kernel/select.rb +7 -0
- data/lib/agent/notifier.rb +34 -0
- data/lib/agent/once.rb +32 -0
- data/lib/agent/pop.rb +70 -0
- data/lib/agent/push.rb +70 -0
- data/lib/agent/queue.rb +133 -0
- data/lib/agent/queue/buffered.rb +68 -0
- data/lib/agent/queue/unbuffered.rb +88 -0
- data/lib/agent/queues.rb +50 -0
- data/lib/agent/selector.rb +119 -0
- data/lib/agent/uuid.rb +36 -0
- data/lib/agent/version.rb +1 -1
- data/lib/agent/wait_group.rb +43 -0
- data/spec/blocking_once_spec.rb +122 -0
- data/spec/channel_spec.rb +153 -82
- data/spec/error_spec.rb +15 -0
- data/spec/examples/channel_of_channels_spec.rb +17 -14
- data/spec/examples/producer_consumer_spec.rb +26 -16
- data/spec/examples/sieve_spec.rb +43 -37
- data/spec/go_spec.rb +17 -0
- data/spec/notifier_spec.rb +42 -0
- data/spec/once_spec.rb +91 -0
- data/spec/pop_spec.rb +97 -0
- data/spec/push_spec.rb +95 -0
- data/spec/queue_spec.rb +335 -37
- data/spec/queues_spec.rb +28 -0
- data/spec/selector_spec.rb +398 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/uuid_spec.rb +13 -0
- data/spec/wait_group_spec.rb +45 -0
- metadata +94 -54
- data/lib/agent/transport/queue.rb +0 -82
- data/spec/helper.rb +0 -5
data/lib/agent/uuid.rb
ADDED
@@ -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
|
data/lib/agent/version.rb
CHANGED
@@ -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
|
data/spec/channel_spec.rb
CHANGED
@@ -1,38 +1,17 @@
|
|
1
|
-
require "
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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 "
|
33
|
-
c =
|
34
|
-
|
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 =
|
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
|
-
|
53
|
-
|
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 =
|
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
|
-
|
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
|
-
|
67
|
-
|
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 {
|
79
|
-
|
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
|
-
|
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.
|
146
|
+
c.close
|
86
147
|
end
|
87
148
|
end
|
88
149
|
|
89
|
-
context "
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
156
|
+
it "should default to unbuffered" do
|
157
|
+
n = Time.now
|
158
|
+
c = channel!(String)
|
104
159
|
|
105
|
-
|
106
|
-
|
160
|
+
go!{ sleep(0.15); c.send("hello") }
|
161
|
+
c.receive[0].should == "hello"
|
107
162
|
|
108
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
120
|
-
|
170
|
+
c.send "hello 1"
|
171
|
+
c.send "hello 2"
|
121
172
|
|
122
|
-
|
123
|
-
c
|
124
|
-
|
173
|
+
select! do |s|
|
174
|
+
s.case(c, :send, "hello 3")
|
175
|
+
s.timeout(0.1)
|
176
|
+
end
|
125
177
|
|
126
|
-
|
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
|
-
|
130
|
-
|
185
|
+
c.close
|
186
|
+
end
|
187
|
+
end
|
131
188
|
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
141
|
-
|
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
|
|