agent 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in go.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ agent (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.2)
10
+ rspec (2.0.1)
11
+ rspec-core (~> 2.0.1)
12
+ rspec-expectations (~> 2.0.1)
13
+ rspec-mocks (~> 2.0.1)
14
+ rspec-core (2.0.1)
15
+ rspec-expectations (2.0.1)
16
+ diff-lcs (>= 1.1.2)
17
+ rspec-mocks (2.0.1)
18
+ rspec-core (~> 2.0.1)
19
+ rspec-expectations (~> 2.0.1)
20
+
21
+ PLATFORMS
22
+ java
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ agent!
27
+ rspec
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # Agent
2
+
3
+ Agent is a diverse family of related approaches for modelling concurrent systems, in Ruby. In other words, it is a collection of different [process calculi](http://en.wikipedia.org/wiki/Process_calculus) primitives and patterns, with no specific, idiomatic affiliation to any specific implementation. A few available patterns so far:
4
+
5
+ - Goroutines on top of green Ruby threads
6
+ - Named, in-memory channels
7
+
8
+ This gem is a work in progress & an experiment, so treat it as such. At the moment, it is heavily influenced by Google's Go and π-calculus primitives.
9
+
10
+ # Working Code Examples
11
+
12
+ * [Producer, Consumer with Goroutines](https://github.com/igrigorik/agent/blob/master/spec/examples/producer_consumer_spec.rb)
13
+ * [Serializable Channels / Event-driven server](https://github.com/igrigorik/agent/blob/master/spec/examples/channel_of_channels_spec.rb)
14
+ * [Sieve of Eratosthenes](https://github.com/igrigorik/agent/blob/master/spec/examples/sieve_spec.rb)
15
+
16
+ # Example: Goroutine Generator
17
+
18
+ producer = Proc.new do |c|
19
+ puts "Starting generator: #{c.name}"
20
+
21
+ i = 0
22
+ loop { c.pipe << i+= 1 }
23
+ end
24
+
25
+ c = Agent::Channel.new(name: :incr, type: Integer)
26
+
27
+ Generator = Struct.new(:name, :pipe)
28
+ g = Generator.new(:incr, c)
29
+
30
+ go(g, &producer)
31
+
32
+ c.receive
33
+ c.receive
34
+
35
+
36
+ # Go & π-calculus: Background & Motivation
37
+
38
+ *Do not communicate by sharing memory; instead, share memory by communicating.*
39
+
40
+ Concurrent programming in many environments is made difficult by the subtleties required to implement correct access to shared variables. Google's Go encourages a different approach in which shared values are passed around on channels and, in fact, never actively shared by separate threads of execution. Only one goroutine has access to the value at any given time. Data races cannot occur, by design.
41
+
42
+ One way to think about this model is to consider a typical single-threaded program running on one CPU. It has no need for synchronization primitives. Now run another such instance; it too needs no synchronization. Now let those two communicate; if the communication is the synchronizer, there's still no need for other synchronization. Unix pipelines, for example, fit this model perfectly. Although Go's approach to concurrency originates in Hoare's Communicating Sequential Processes (CSP), it can also be seen as a type-safe generalization of Unix pipes.
43
+
44
+ To learn more about Go see following resources:
45
+
46
+ * [golang.org](http://golang.org/)
47
+ * [Go's concurrency](http://golang.org/doc/effective_go.html#concurrency)
48
+ * [Go's channels](http://golang.org/doc/effective_go.html#channels)
49
+
50
+ # License
51
+
52
+ (The MIT License)
53
+
54
+ Copyright (c) 2010 Ilya Grigorik
55
+
56
+ Permission is hereby granted, free of charge, to any person obtaining
57
+ a copy of this software and associated documentation files (the
58
+ 'Software'), to deal in the Software without restriction, including
59
+ without limitation the rights to use, copy, modify, merge, publish,
60
+ distribute, sublicense, and/or sell copies of the Software, and to
61
+ permit persons to whom the Software is furnished to do so, subject to
62
+ the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be
65
+ included in all copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
68
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
69
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
70
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
71
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
72
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
73
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/agent.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "agent/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "agent"
7
+ s.version = Agent::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ilya Grigorik"]
10
+ s.email = ["ilya@igvita.com"]
11
+ s.homepage = "https://github.com/igrigorik/agent"
12
+ s.summary = %q{Agent is a diverse family of related approaches for modelling concurrent systems, in Ruby}
13
+ s.description = s.summary
14
+
15
+ s.rubyforge_project = "agent"
16
+ s.add_development_dependency "rspec"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+ end
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { 'rspec2' }
data/lib/agent.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'agent/channel'
2
+ require 'agent/transport/queue'
3
+
4
+ module Kernel
5
+ def go(*args, &blk)
6
+ Thread.new do
7
+ begin
8
+ blk.call(*args)
9
+ rescue Exception => e
10
+ p e
11
+ p e.backtrace
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,74 @@
1
+ # Channels combine communication—the exchange of a value—with synchronization—guaranteeing
2
+ # that two calculations (goroutines) are in a known state.
3
+ # - http://golang.org/doc/effective_go.html#channels
4
+
5
+ module Agent
6
+ class Channel
7
+ attr_reader :name, :transport, :chan
8
+
9
+ def initialize(opts = {})
10
+ @state = :active
11
+ @name = opts[:name]
12
+ @max = opts[:size] || 1
13
+ @type = opts[:type]
14
+ @direction = opts[:direction] || :bidirectional
15
+ @transport = opts[:transport] || Agent::Transport::Queue
16
+
17
+ raise NoName if @name.nil?
18
+ raise Untyped if @type.nil?
19
+
20
+ @chan = @transport.new(@name, @max)
21
+ end
22
+
23
+ def marshal_load(ary)
24
+ @state, @name, @type, @direction, @transport = *ary
25
+ @chan = @transport.new(@name)
26
+ self
27
+ end
28
+
29
+ def marshal_dump
30
+ [@state, @name, @type, @direction, @transport]
31
+ end
32
+
33
+ def send(msg)
34
+ check_direction(:send)
35
+ check_type(msg)
36
+
37
+ @chan.send(Marshal.dump(msg))
38
+ end
39
+ alias :push :send
40
+ alias :<< :send
41
+
42
+ def receive
43
+ check_direction(:receive)
44
+
45
+ msg = Marshal.load(@chan.receive)
46
+ check_type(msg)
47
+
48
+ msg
49
+ end
50
+ alias :pop :receive
51
+
52
+ def closed?; @state == :closed; end
53
+ def close
54
+ @chan.close
55
+ @state = :closed
56
+ end
57
+
58
+ private
59
+
60
+ def check_type(msg)
61
+ raise InvalidType if !msg.is_a? @type
62
+ end
63
+
64
+ def check_direction(direction)
65
+ return if @direction == :bidirectional
66
+ raise InvalidDirection if @direction != direction
67
+ end
68
+
69
+ class InvalidDirection < Exception; end
70
+ class NoName < Exception; end
71
+ class Untyped < Exception; end
72
+ class InvalidType < Exception; end
73
+ end
74
+ end
@@ -0,0 +1,82 @@
1
+ module Agent
2
+ module Transport
3
+
4
+ class Queue
5
+
6
+ @@registry = {}
7
+
8
+ def initialize(name, max = 1)
9
+ raise ArgumentError, "queue size must be positive" unless max > 0
10
+
11
+ @name = name
12
+ @max = max
13
+
14
+ if !@@registry[@name]
15
+ @@registry[@name] = {
16
+ :que => [],
17
+ :wait => [],
18
+ :mutex => Mutex.new,
19
+ :cvar => ConditionVariable.new
20
+ }
21
+ end
22
+ end
23
+
24
+ def data; @@registry[@name]; end
25
+ def que; data[:que]; end
26
+ def wait; data[:wait]; end
27
+ def mutex; data[:mutex]; end
28
+ def cvar; data[:cvar]; end
29
+
30
+ def max; @max; end
31
+ def size; que.size; end
32
+ def length; que.size; end
33
+
34
+ def push(obj)
35
+ mutex.synchronize {
36
+ while true
37
+ break if que.length < @max
38
+ cvar.wait(mutex)
39
+ end
40
+
41
+ que.push obj
42
+ cvar.signal
43
+ }
44
+ end
45
+ alias << push
46
+ alias enq push
47
+
48
+ def pop(*args)
49
+ mutex.synchronize {
50
+ while true
51
+ break if !que.empty?
52
+ cvar.wait(mutex)
53
+ end
54
+
55
+ retval = que.shift
56
+ cvar.signal
57
+
58
+ retval
59
+ }
60
+ end
61
+ alias shift pop
62
+ alias deq pop
63
+
64
+ def async?; @max > 1; end
65
+
66
+ def send(msg, nonblock = false)
67
+ raise ThreadError, "buffer full" if nonblock && que.length >= @max
68
+ push(msg)
69
+ end
70
+
71
+ def receive(nonblock = false)
72
+ raise ThreadError, "buffer empty" if nonblock && que.empty?
73
+ pop
74
+ end
75
+
76
+ def close
77
+ @@registry.delete @name
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ module Agent
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,145 @@
1
+ require "helper"
2
+
3
+ describe Agent::Channel do
4
+ # http://golang.org/doc/go_spec.html#Channel_types
5
+
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
29
+ c.close
30
+ end
31
+
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
36
+ c.close
37
+ end
38
+ end
39
+
40
+ context "direction" do
41
+ # A channel provides a mechanism for two concurrently executing functions to
42
+ # synchronize execution and communicate by passing a value of a specified element
43
+ # type. The value of an uninitialized channel is nil.
44
+
45
+ it "should support send only" do
46
+ c = Channel.new(:name => "spec", :direction => :send, :type => String, :size => 3)
47
+
48
+ lambda { c << "hello" }.should_not raise_error
49
+ lambda { c.push "hello" }.should_not raise_error
50
+ lambda { c.send "hello" }.should_not raise_error
51
+
52
+ lambda { c.pop }.should raise_error Channel::InvalidDirection
53
+ lambda { c.receive }.should raise_error Channel::InvalidDirection
54
+
55
+ c.close
56
+ end
57
+
58
+ it "should support receive only" do
59
+ c = Channel.new(:name => "spec", :direction => :receive, :type => String)
60
+
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
64
+
65
+ # 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)
68
+ end
69
+
70
+ it "should default to bi-directional communication" do
71
+ lambda { c.send "hello" }.should_not raise_error
72
+ lambda { c.receive }.should_not raise_error
73
+ end
74
+ end
75
+
76
+ context "typed" do
77
+ 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
80
+ end
81
+
82
+ it "should reject messages of invalid type" do
83
+ lambda { c.send 1 }.should raise_error(Channel::InvalidType)
84
+ lambda { c.send "hello" }.should_not raise_error
85
+ c.receive
86
+ end
87
+ end
88
+
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
99
+
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
104
+
105
+ it "should be able to pass as a value on a different channel" do
106
+ c.send "hello"
107
+
108
+ cm = Marshal.load(Marshal.dump(c))
109
+ cm.receive.should == "hello"
110
+ end
111
+ end
112
+
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.
118
+
119
+ it "should default to synchronous communication" do
120
+ c = Channel.new(:name => "buffered", :type => String)
121
+
122
+ c.send "hello"
123
+ c.receive.should == "hello"
124
+ lambda { Timeout::timeout(0.1) { c.receive } }.should raise_error(Timeout::Error)
125
+
126
+ c.close
127
+ end
128
+
129
+ it "should support asynchronous communication with buffered capacity" do
130
+ c = Channel.new(:name => "buffered", :type => String, :size => 2)
131
+
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)
135
+
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)
139
+
140
+ c.close
141
+ end
142
+ end
143
+ end
144
+
145
+ end
@@ -0,0 +1,59 @@
1
+ require "helper"
2
+
3
+ describe "Channel of Channels" do
4
+
5
+ Request = Struct.new(:args, :resultChan)
6
+
7
+ it "should be able to pass channels as first class citizens" do
8
+ server = Proc.new do |reqs|
9
+ 2.times do |n|
10
+ res = Request.new(n, Agent::Channel.new(:name => "resultChan-#{n}", :type => Integer))
11
+
12
+ reqs << res
13
+ res.resultChan.receive.should == n+1
14
+ end
15
+ end
16
+
17
+ worker = Proc.new do |reqs|
18
+ loop do
19
+ req = reqs.receive
20
+ req.resultChan << req.args+1
21
+ end
22
+ end
23
+
24
+ clientRequests = Agent::Channel.new(:name => :clientRequests, :type => Request)
25
+
26
+ s = go(clientRequests, &server)
27
+ c = go(clientRequests, &worker)
28
+
29
+ s.join
30
+ clientRequests.close
31
+ end
32
+
33
+ it "should work with multiple workers" do
34
+ worker = Proc.new do |reqs|
35
+ loop do
36
+ req = reqs.receive
37
+ req.resultChan << req.args+1
38
+ end
39
+ end
40
+
41
+ clientRequests = Agent::Channel.new(:name => :clientRequests, :type => Request)
42
+
43
+ # start multiple workers
44
+ go(clientRequests, &worker)
45
+ go(clientRequests, &worker)
46
+
47
+ # start server
48
+ s = go clientRequests do |reqs|
49
+ 2.times do |n|
50
+ res = Request.new(n, Agent::Channel.new(:name => "resultChan-#{n}", :type => Integer))
51
+
52
+ reqs << res
53
+ res.resultChan.receive.should == n+1
54
+ end
55
+ end
56
+
57
+ s.join
58
+ end
59
+ end
@@ -0,0 +1,34 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "runtime"
6
+ )
7
+
8
+ func producer(c chan int, N int, s chan bool) {
9
+ for i := 0; i < N; i++ {
10
+ fmt.Printf("producer: %d\n", i)
11
+ c <- i
12
+ }
13
+ s <- true
14
+ }
15
+
16
+ func consumer(c chan int, N int, s chan bool) {
17
+ for i := 0; i < N; i++ {
18
+ fmt.Printf("consumer got: %d\n", <- c)
19
+ }
20
+ s <- true
21
+ }
22
+
23
+ func main() {
24
+ runtime.GOMAXPROCS(2)
25
+
26
+ c := make(chan int)
27
+ s := make(chan bool)
28
+
29
+ go producer(c, 10, s)
30
+ go consumer(c, 10, s)
31
+
32
+ <- s
33
+ <- s
34
+ }
@@ -0,0 +1,76 @@
1
+ // Copyright 2009 The Go Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ package main
6
+
7
+ import (
8
+ "flag"
9
+ "fmt"
10
+ "os"
11
+ "strconv"
12
+ )
13
+
14
+ var nth = flag.Bool("n", false, "print the nth prime only")
15
+
16
+ // Send the sequence 2, 3, 4, ... to returned channel
17
+ func generate() chan int {
18
+ ch := make(chan int)
19
+ go func() {
20
+ for i := 2; ; i++ {
21
+ ch <- i
22
+ }
23
+ }()
24
+ return ch
25
+ }
26
+
27
+ // Filter out input values divisible by 'prime', send rest to returned channel
28
+ func filter(in chan int, prime int) chan int {
29
+ out := make(chan int)
30
+ go func() {
31
+ for {
32
+ if i := <-in; i%prime != 0 {
33
+ out <- i
34
+ }
35
+ }
36
+ }()
37
+ return out
38
+ }
39
+
40
+ func sieve() chan int {
41
+ out := make(chan int)
42
+ go func() {
43
+ ch := generate()
44
+ for {
45
+ prime := <-ch
46
+ out <- prime
47
+ ch = filter(ch, prime)
48
+ }
49
+ }()
50
+ return out
51
+ }
52
+
53
+ func main() {
54
+ flag.Parse()
55
+ n, err := strconv.Atoi(flag.Arg(0))
56
+ if err != nil {
57
+ fmt.Fprintf(os.Stderr, "bad argument\n")
58
+ os.Exit(1)
59
+ }
60
+ primes := sieve()
61
+ if *nth {
62
+ for i := 1; i < n; i++ {
63
+ <-primes
64
+ }
65
+ fmt.Println(<-primes)
66
+ } else {
67
+ for {
68
+ p := <-primes
69
+ if p <= n {
70
+ fmt.Println(p)
71
+ } else {
72
+ return
73
+ }
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,83 @@
1
+ require "helper"
2
+
3
+ describe "Producer-Consumer" do
4
+ it "should synchronize by communication" do
5
+
6
+ # func producer(c chan int, N int, s chan bool) {
7
+ # for i := 0; i < N; i++ {
8
+ # fmt.Printf("producer: %d\n", i)
9
+ # c <- i
10
+ # }
11
+ # s <- true
12
+ # }
13
+ #
14
+ # func consumer(c chan int, N int, s chan bool) {
15
+ # for i := 0; i < N; i++ {
16
+ # fmt.Printf("consumer got: %d\n", <- c)
17
+ # }
18
+ # s <- true
19
+ # }
20
+ #
21
+ # func main() {
22
+ # runtime.GOMAXPROCS(2)
23
+ #
24
+ # c := make(chan int)
25
+ # s := make(chan bool)
26
+ #
27
+ # go producer(c, 10, s)
28
+ # go consumer(c, 10, s)
29
+ #
30
+ # <- s
31
+ # <- s
32
+ # }
33
+
34
+ producer = Proc.new do |c, n, s|
35
+ n.times do |i|
36
+ c << i
37
+ # puts "producer: #{i}"
38
+ end
39
+
40
+ s << "producer finished"
41
+ end
42
+
43
+ consumer = Proc.new do |c, n, s|
44
+ n.times do |i|
45
+ msg = c.receive
46
+ # puts "consumer got: #{msg}"
47
+ end
48
+
49
+ s << "consumer finished"
50
+ end
51
+
52
+ c = Agent::Channel.new(name: :c, type: Integer)
53
+ s = Agent::Channel.new(name: :s, type: String)
54
+
55
+ go(c, 3, s, &producer)
56
+ go(c, 3, s, &consumer)
57
+
58
+ s.pop.should == "producer finished"
59
+ s.pop.should == "consumer finished"
60
+
61
+ c.close
62
+ s.close
63
+ end
64
+
65
+ it "should work as generator" do
66
+ producer = Proc.new do |c|
67
+ i = 0
68
+ loop { c.pipe << i+= 1 }
69
+ end
70
+
71
+ Generator = Struct.new(:name, :pipe)
72
+ c = Agent::Channel.new(name: :incr, type: Integer)
73
+ g = Generator.new(:incr, c)
74
+
75
+ go(g, &producer)
76
+
77
+ c.receive.should == 1
78
+ c.receive.should == 2
79
+
80
+ c.chan.size.should == 0
81
+ c.receive.should == 3
82
+ end
83
+ end
@@ -0,0 +1,143 @@
1
+ require "helper"
2
+
3
+ describe "sieve of Eratosthenes" do
4
+
5
+ # http://golang.org/doc/go_tutorial.html#tmp_353
6
+
7
+ it "should work using Channel primitives" do
8
+
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
17
+
18
+ return ch
19
+ end
20
+
21
+ # 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)
24
+
25
+ go do
26
+ loop do
27
+ i = in_channel.receive
28
+ out << i if (i % prime) != 0
29
+ end
30
+ end
31
+
32
+ return out
33
+ end
34
+
35
+ def sieve
36
+ out = Agent::Channel.new(name: 'sieve', type: Integer)
37
+
38
+ go do
39
+ ch = generate
40
+ loop do
41
+ prime = ch.receive
42
+ out << prime
43
+ ch = filter(ch, prime)
44
+ end
45
+ end
46
+
47
+ return out
48
+ end
49
+
50
+ # run the sieve
51
+ n = 20
52
+ nth = false
53
+
54
+ primes = sieve
55
+ result = []
56
+
57
+ if nth
58
+ n.times { primes.receive }
59
+ puts primes.receive
60
+ else
61
+ loop do
62
+ p = primes.receive
63
+
64
+ if p <= n
65
+ result << p
66
+ else
67
+ break
68
+ end
69
+ end
70
+ end
71
+
72
+ result.should == [2,3,5,7,11,13,17,19]
73
+ end
74
+
75
+ it "should work with Ruby blocks" do
76
+
77
+ # send the sequence 2,3,4, ... to returned channel
78
+ generate = Proc.new do
79
+ ch = Agent::Channel.new(name: 'generator-block', type: Integer)
80
+
81
+ go do
82
+ i = 1
83
+ loop { ch << i+= 1 }
84
+ end
85
+
86
+ ch
87
+ end
88
+
89
+ # 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
+
93
+ go do
94
+ loop do
95
+ i = in_channel.receive
96
+ out << i if (i % prime) != 0
97
+ end
98
+ end
99
+
100
+ out
101
+ end
102
+
103
+ sieve = -> do
104
+ out = Agent::Channel.new(name: 'sieve-block', type: Integer)
105
+
106
+ go do
107
+ ch = generate.call
108
+
109
+ loop do
110
+ prime = ch.receive
111
+ out << prime
112
+ ch = filtr.call(ch, prime)
113
+ end
114
+ end
115
+
116
+ out
117
+ end
118
+
119
+ # run the sieve
120
+ n = 20
121
+ nth = false
122
+
123
+ primes = sieve.call
124
+ result = []
125
+
126
+ if nth
127
+ n.times { primes.receive }
128
+ puts primes.receive
129
+ else
130
+ loop do
131
+ p = primes.receive
132
+
133
+ if p <= n
134
+ result << p
135
+ else
136
+ break
137
+ end
138
+ end
139
+ end
140
+
141
+ result.should == [2,3,5,7,11,13,17,19]
142
+ end
143
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'timeout'
2
+ require 'rspec'
3
+ require 'yaml'
4
+
5
+ require 'lib/agent'
@@ -0,0 +1,53 @@
1
+ require 'helper'
2
+
3
+ describe Agent::Transport::Queue do
4
+ include Agent::Transport
5
+
6
+ it "should support synchronous, unbuffered communication" do
7
+ lambda { Queue.new("spec") }.should_not raise_error
8
+
9
+ q = Queue.new("spec")
10
+ q.max.should == 1
11
+ q.async?.should be_false
12
+
13
+ lambda { q.send("hello") }.should_not raise_error
14
+ lambda { q.send("hello", true) }.should raise_error(ThreadError, "buffer full")
15
+
16
+ q.receive.should == "hello"
17
+ lambda { q.receive(true) }.should raise_error(ThreadError, "buffer empty")
18
+ end
19
+
20
+ it "should support asynchronous, buffered communication" do
21
+ lambda { Queue.new("spec", 2) }.should_not raise_error
22
+
23
+ q = Queue.new("spec", 2)
24
+ q.max.should == 2
25
+ q.async?.should be_true
26
+
27
+ lambda { q.send("hello 1") }.should_not raise_error
28
+ lambda { q.send("hello 2", true) }.should_not raise_error(ThreadError, "buffer full")
29
+ lambda { q.send("hello 3", true) }.should raise_error(ThreadError, "buffer full")
30
+
31
+ q.receive.should == "hello 1"
32
+ q.receive.should == "hello 2"
33
+ lambda { q.receive(true) }.should raise_error(ThreadError, "buffer empty")
34
+ end
35
+
36
+ it "should persist data between queue objects" do
37
+ q = Queue.new("spec")
38
+ q.send "hello"
39
+
40
+ q = Queue.new("spec")
41
+ q.receive.should == "hello"
42
+ end
43
+
44
+ it "should clear registry on close" do
45
+ q = Queue.new("spec")
46
+ q.send "hello"
47
+ q.close
48
+
49
+ q = Queue.new("spec")
50
+ lambda { q.receive(true) }.should raise_error(ThreadError, "buffer empty")
51
+ end
52
+
53
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: agent
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Ilya Grigorik
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-10 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ description: Agent is a diverse family of related approaches for modelling concurrent systems, in Ruby
34
+ email:
35
+ - ilya@igvita.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - .gitignore
44
+ - Gemfile
45
+ - Gemfile.lock
46
+ - README.md
47
+ - Rakefile
48
+ - agent.gemspec
49
+ - autotest/discover.rb
50
+ - lib/agent.rb
51
+ - lib/agent/channel.rb
52
+ - lib/agent/transport/queue.rb
53
+ - lib/agent/version.rb
54
+ - spec/channel_spec.rb
55
+ - spec/examples/channel_of_channels_spec.rb
56
+ - spec/examples/go/producer_consumer.go
57
+ - spec/examples/go/sieve.go
58
+ - spec/examples/producer_consumer_spec.rb
59
+ - spec/examples/sieve_spec.rb
60
+ - spec/helper.rb
61
+ - spec/queue_spec.rb
62
+ has_rdoc: true
63
+ homepage: https://github.com/igrigorik/agent
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ requirements: []
88
+
89
+ rubyforge_project: agent
90
+ rubygems_version: 1.3.7
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Agent is a diverse family of related approaches for modelling concurrent systems, in Ruby
94
+ test_files:
95
+ - spec/channel_spec.rb
96
+ - spec/examples/channel_of_channels_spec.rb
97
+ - spec/examples/go/producer_consumer.go
98
+ - spec/examples/go/sieve.go
99
+ - spec/examples/producer_consumer_spec.rb
100
+ - spec/examples/sieve_spec.rb
101
+ - spec/helper.rb
102
+ - spec/queue_spec.rb