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