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,15 @@
1
+ require "spec_helper"
2
+
3
+ describe Agent::Error do
4
+ before do
5
+ @error = Agent::Error.new("msg")
6
+ end
7
+
8
+ it "should create an error" do
9
+ @error.to_s.should == "msg"
10
+ end
11
+
12
+ it "should match the error's message" do
13
+ @error.should be_message("msg")
14
+ end
15
+ end
@@ -1,4 +1,4 @@
1
- require "helper"
1
+ require "spec_helper"
2
2
 
3
3
  describe "Channel of Channels" do
4
4
 
@@ -7,24 +7,26 @@ describe "Channel of Channels" do
7
7
  it "should be able to pass channels as first class citizens" do
8
8
  server = Proc.new do |reqs|
9
9
  2.times do |n|
10
- res = Request.new(n, Agent::Channel.new(:name => "resultChan-#{n}", :type => Integer))
10
+ res = Request.new(n, channel!(Integer))
11
11
 
12
12
  reqs << res
13
- res.resultChan.receive.should == n+1
13
+ res.resultChan.receive[0].should == n+1
14
+ res.resultChan.close
14
15
  end
15
16
  end
16
17
 
17
18
  worker = Proc.new do |reqs|
18
19
  loop do
19
- req = reqs.receive
20
+ req, ok = reqs.receive
21
+ break unless ok
20
22
  req.resultChan << req.args+1
21
23
  end
22
24
  end
23
25
 
24
- clientRequests = Agent::Channel.new(:name => :clientRequests, :type => Request)
26
+ clientRequests = channel!(Request)
25
27
 
26
- s = go(clientRequests, &server)
27
- c = go(clientRequests, &worker)
28
+ s = go!(clientRequests, &server)
29
+ c = go!(clientRequests, &worker)
28
30
 
29
31
  s.join
30
32
  clientRequests.close
@@ -33,24 +35,25 @@ describe "Channel of Channels" do
33
35
  it "should work with multiple workers" do
34
36
  worker = Proc.new do |reqs|
35
37
  loop do
36
- req = reqs.receive
38
+ req, _ = reqs.receive
37
39
  req.resultChan << req.args+1
38
40
  end
39
41
  end
40
42
 
41
- clientRequests = Agent::Channel.new(:name => :clientRequests, :type => Request)
43
+ clientRequests = channel!(Request)
42
44
 
43
45
  # start multiple workers
44
- go(clientRequests, &worker)
45
- go(clientRequests, &worker)
46
+ go!(clientRequests, &worker)
47
+ go!(clientRequests, &worker)
46
48
 
47
49
  # start server
48
- s = go clientRequests do |reqs|
50
+ s = go! clientRequests do |reqs|
49
51
  2.times do |n|
50
- res = Request.new(n, Agent::Channel.new(:name => "resultChan-#{n}", :type => Integer))
52
+ res = Request.new(n, channel!(Integer))
51
53
 
52
54
  reqs << res
53
- res.resultChan.receive.should == n+1
55
+ res.resultChan.receive[0].should == n+1
56
+ res.resultChan.close
54
57
  end
55
58
  end
56
59
 
@@ -1,4 +1,4 @@
1
- require "helper"
1
+ require "spec_helper"
2
2
 
3
3
  describe "Producer-Consumer" do
4
4
  it "should synchronize by communication" do
@@ -32,31 +32,42 @@ describe "Producer-Consumer" do
32
32
  # }
33
33
 
34
34
  producer = Proc.new do |c, n, s|
35
+ # print "producer: starting\n"
36
+
35
37
  n.times do |i|
38
+ # print "producer: #{i+1} of #{n}\n"
36
39
  c << i
37
- # puts "producer: #{i}"
40
+ # print "producer sent: #{i}\n"
38
41
  end
39
42
 
43
+ # print "producer: finished\n"
44
+
40
45
  s << "producer finished"
41
46
  end
42
47
 
43
48
  consumer = Proc.new do |c, n, s|
49
+ # print "consumer: starting\n"
44
50
  n.times do |i|
51
+ # print "consumer: #{i+1} of #{n}\n"
45
52
  msg = c.receive
46
- # puts "consumer got: #{msg}"
53
+ # print "consumer got: #{msg}\n"
47
54
  end
48
55
 
56
+ # print "consumer: finished\n"
57
+
49
58
  s << "consumer finished"
50
59
  end
51
60
 
52
- c = Agent::Channel.new(name: :c, type: Integer)
53
- s = Agent::Channel.new(name: :s, type: String)
61
+ c = channel!(Integer)
62
+ s = channel!(String)
54
63
 
55
- go(c, 3, s, &producer)
56
- go(c, 3, s, &consumer)
64
+ go!(c, 3, s, &producer)
65
+ sleep 0.1
66
+ go!(c, 3, s, &consumer)
57
67
 
58
- s.pop.should == "producer finished"
59
- s.pop.should == "consumer finished"
68
+ messages = [s.pop[0], s.pop[0]]
69
+ messages.should include("producer finished")
70
+ messages.should include("consumer finished")
60
71
 
61
72
  c.close
62
73
  s.close
@@ -69,15 +80,14 @@ describe "Producer-Consumer" do
69
80
  end
70
81
 
71
82
  Generator = Struct.new(:name, :pipe)
72
- c = Agent::Channel.new(name: :incr, type: Integer)
83
+ c = channel!(Integer)
73
84
  g = Generator.new(:incr, c)
74
85
 
75
- go(g, &producer)
86
+ go!(g, &producer)
76
87
 
77
- c.receive.should == 1
78
- c.receive.should == 2
79
-
80
- c.chan.size.should == 0
81
- c.receive.should == 3
88
+ c.receive[0].should == 1
89
+ c.receive[0].should == 2
90
+ c.receive[0].should == 3
91
+ c.close
82
92
  end
83
93
  end
@@ -1,4 +1,4 @@
1
- require "helper"
1
+ require "spec_helper"
2
2
 
3
3
  describe "sieve of Eratosthenes" do
4
4
 
@@ -7,24 +7,22 @@ describe "sieve of Eratosthenes" do
7
7
  it "should work using Channel primitives" do
8
8
 
9
9
  # send the sequence 2,3,4, ... to returned channel
10
- def generate
11
- ch = Agent::Channel.new(name: 'generator', type: Integer)
12
-
13
- go do
14
- i = 1
15
- loop { ch << i+= 1 }
16
- end
10
+ def generate(channels)
11
+ ch = channel!(Integer)
12
+ channels << ch
13
+ go!{ i = 1; loop { ch << i+= 1} }
17
14
 
18
15
  return ch
19
16
  end
20
17
 
21
18
  # filter out input values divisible by *prime*, send rest to returned channel
22
- def filter(in_channel, prime)
23
- out = Agent::Channel.new(name: "filter-#{prime}", type: Integer)
19
+ def filter(in_channel, prime, channels)
20
+ out = channel!(Integer)
21
+ channels << out
24
22
 
25
- go do
23
+ go! do
26
24
  loop do
27
- i = in_channel.receive
25
+ i, _ = in_channel.receive
28
26
  out << i if (i % prime) != 0
29
27
  end
30
28
  end
@@ -32,15 +30,16 @@ describe "sieve of Eratosthenes" do
32
30
  return out
33
31
  end
34
32
 
35
- def sieve
36
- out = Agent::Channel.new(name: 'sieve', type: Integer)
33
+ def sieve(channels)
34
+ out = channel!(Integer)
35
+ channels << out
37
36
 
38
- go do
39
- ch = generate
37
+ go! do
38
+ ch = generate(channels)
40
39
  loop do
41
- prime = ch.receive
40
+ prime, _ = ch.receive
42
41
  out << prime
43
- ch = filter(ch, prime)
42
+ ch = filter(ch, prime, channels)
44
43
  end
45
44
  end
46
45
 
@@ -50,16 +49,17 @@ describe "sieve of Eratosthenes" do
50
49
  # run the sieve
51
50
  n = 20
52
51
  nth = false
52
+ channels = []
53
53
 
54
- primes = sieve
54
+ primes = sieve(channels)
55
55
  result = []
56
56
 
57
57
  if nth
58
58
  n.times { primes.receive }
59
- puts primes.receive
59
+ puts primes.receive[0]
60
60
  else
61
61
  loop do
62
- p = primes.receive
62
+ p, _ = primes.receive
63
63
 
64
64
  if p <= n
65
65
  result << p
@@ -70,15 +70,17 @@ describe "sieve of Eratosthenes" do
70
70
  end
71
71
 
72
72
  result.should == [2,3,5,7,11,13,17,19]
73
+ channels.each(&:close)
73
74
  end
74
75
 
75
76
  it "should work with Ruby blocks" do
76
77
 
77
78
  # send the sequence 2,3,4, ... to returned channel
78
- generate = Proc.new do
79
- ch = Agent::Channel.new(name: 'generator-block', type: Integer)
79
+ generate = Proc.new do |channels|
80
+ ch = channel!(Integer)
81
+ channels << ch
80
82
 
81
- go do
83
+ go! do
82
84
  i = 1
83
85
  loop { ch << i+= 1 }
84
86
  end
@@ -87,12 +89,13 @@ describe "sieve of Eratosthenes" do
87
89
  end
88
90
 
89
91
  # filter out input values divisible by *prime*, send rest to returned channel
90
- filtr = Proc.new do |in_channel, prime|
91
- out = Agent::Channel.new(name: "filter-#{prime}-block", type: Integer)
92
+ filtr = Proc.new do |in_channel, prime, channels|
93
+ out = channel!(Integer)
94
+ channels << out
92
95
 
93
- go do
96
+ go! do
94
97
  loop do
95
- i = in_channel.receive
98
+ i, _ = in_channel.receive
96
99
  out << i if (i % prime) != 0
97
100
  end
98
101
  end
@@ -100,16 +103,17 @@ describe "sieve of Eratosthenes" do
100
103
  out
101
104
  end
102
105
 
103
- sieve = -> do
104
- out = Agent::Channel.new(name: 'sieve-block', type: Integer)
106
+ sieve = Proc.new do |channels|
107
+ out = channel!(Integer)
108
+ channels << out
105
109
 
106
- go do
107
- ch = generate.call
110
+ go! do
111
+ ch = generate.call(channels)
108
112
 
109
113
  loop do
110
- prime = ch.receive
114
+ prime, _ = ch.receive
111
115
  out << prime
112
- ch = filtr.call(ch, prime)
116
+ ch = filtr.call(ch, prime, channels)
113
117
  end
114
118
  end
115
119
 
@@ -119,16 +123,17 @@ describe "sieve of Eratosthenes" do
119
123
  # run the sieve
120
124
  n = 20
121
125
  nth = false
126
+ channels = []
122
127
 
123
- primes = sieve.call
128
+ primes = sieve.call(channels)
124
129
  result = []
125
130
 
126
131
  if nth
127
132
  n.times { primes.receive }
128
- puts primes.receive
133
+ puts primes.receive[0]
129
134
  else
130
135
  loop do
131
- p = primes.receive
136
+ p, _ = primes.receive
132
137
 
133
138
  if p <= n
134
139
  result << p
@@ -139,5 +144,6 @@ describe "sieve of Eratosthenes" do
139
144
  end
140
145
 
141
146
  result.should == [2,3,5,7,11,13,17,19]
147
+ channels.each(&:close)
142
148
  end
143
149
  end
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+
3
+ describe "Agent.go!" do
4
+ it "should launch a 'goroutine' that is actually a thread" do
5
+ Agent.go!{}.should be_a(Thread)
6
+ end
7
+
8
+ it "should pass into the thread any arguments passed to it" do
9
+ b = nil
10
+ Agent.go!(1){|a| b = a }.join
11
+ b.should == 1
12
+ end
13
+
14
+ it "should raise an error if no block is passed" do
15
+ lambda{ Agent.go! }.should raise_error(Agent::Errors::BlockMissing)
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe Agent::Notifier do
4
+ before do
5
+ @notifier = Agent::Notifier.new
6
+ end
7
+
8
+ it "should notify using a payload" do
9
+ @notifier.notify(1)
10
+ @notifier.payload.should == 1
11
+ end
12
+
13
+ it "should acknowledge notification" do
14
+ @notifier.should_not be_notified
15
+ @notifier.notify(1)
16
+ @notifier.should be_notified
17
+ end
18
+
19
+ it "should only notify once" do
20
+ @notifier.notify(1)
21
+ @notifier.notify(2)
22
+ @notifier.payload.should == 1
23
+ end
24
+
25
+ it "should return nil when notified for the first time" do
26
+ @notifier.notify(1).should be_nil
27
+ end
28
+
29
+ it "should return an error when notified more than once" do
30
+ @notifier.notify(1)
31
+ @notifier.notify(2).should be_message("already notified")
32
+ end
33
+
34
+ it "should allow waiting on a notification and should signal when it is notified" do
35
+ ack = channel!(Integer)
36
+ go!{ @notifier.wait; ack.send(@notifier.payload) }
37
+ sleep 0.1 # make sure the notifier in the goroutine is waiting
38
+ @notifier.notify(1)
39
+ payload, _ = ack.receive
40
+ payload.should == 1
41
+ end
42
+ end
@@ -0,0 +1,91 @@
1
+ require "spec_helper"
2
+
3
+ describe Agent::Once do
4
+
5
+ before do
6
+ @once = Agent::Once.new
7
+ end
8
+
9
+ it "should execute the block passed to it" do
10
+ r = []
11
+
12
+ @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
+ @once.perform do
24
+ r << 1
25
+ end
26
+
27
+ @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 = @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 = @once.perform{ 1 }
45
+ value.should == 1
46
+ error.should be_nil
47
+
48
+ value, error = @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 have minimal contention between threads when they contend for position" do
55
+ r, s = [], Time.now.to_f
56
+
57
+ # Using condition variables to maximize potential contention
58
+ mutex = Mutex.new
59
+ condition = ConditionVariable.new
60
+
61
+ waiting_channel = channel!(TrueClass, 2)
62
+ finished_channel = channel!(TrueClass, 2)
63
+
64
+ go! do
65
+ mutex.synchronize{ waiting_channel.send(true); condition.wait(mutex) }
66
+ @once.perform{ sleep 0.1; r << 1 }
67
+ finished_channel.send(true)
68
+ end
69
+
70
+ go! do
71
+ mutex.synchronize{ waiting_channel.send(true); condition.wait(mutex) }
72
+ @once.perform{ sleep 0.1; r << 1 }
73
+ finished_channel.send(true)
74
+ end
75
+
76
+ # wait for both the goroutines to be waiting
77
+ 2.times{ waiting_channel.receive }
78
+
79
+ mutex.synchronize{ condition.broadcast }
80
+
81
+ # wait for the finished channel to be completed
82
+ 2.times{ finished_channel.receive }
83
+
84
+ r.size.should == 1
85
+ # Onlt the first sleep should be performed, so things should quickly
86
+ (Time.now.to_f - s).should be_within(0.05).of(0.15)
87
+
88
+ waiting_channel.close
89
+ finished_channel.close
90
+ end
91
+ end