Permalink
Browse files

Import of gore and making the specs work with the new code

  • Loading branch information...
1 parent 7213c40 commit 492632fdc8196ef191fd28a39ebd49e19d82fd8a @nate nate committed Mar 9, 2012
View
@@ -2,16 +2,16 @@
require 'lib/agent'
def generate(num)
- ch = Agent::Channel.new(name: "generator_#{num}".to_sym, type: Integer)
- go { |i=1| loop { ch << i+= 1} }
+ ch = channel!(:type => Integer)
+ go! { |i=1| loop { ch << i+= 1} }
@igrigorik
igrigorik Mar 13, 2012 owner

Hmm, any particular reason why you're going with the ! convention for channels and 'goroutines'?

@igrigorik
igrigorik Mar 13, 2012 owner

To answer my own question (reading through the code), guessing to avoid the Kernel::select, etc, conflicts.

Maybe it's the novelty factor, but not yet sold on it.. although don't have a much better recommendation (yet).

@nate
nate Mar 13, 2012 collaborator

Yep, that's the issue I'm trying to solve with the new syntax, and since I was already doing it with select, I thought I'd make it consistent across all the Kernel extensions that Agent adds.

return ch
end
def filter(in_channel, prime, num)
- out = Agent::Channel.new(name: "filter_#{prime}_#{num}".to_sym, type: Integer)
+ out = channel!(:type => Integer)
- go do
+ go! do
loop do
i = in_channel.receive
out << i if (i % prime) != 0
@@ -22,9 +22,9 @@ def filter(in_channel, prime, num)
end
def sieve(num)
- out = Agent::Channel.new(name: "sieve_#{num}".to_sym, type: Integer)
+ out = channel!(:type => Integer)
- go do
+ go! do
ch = generate(num)
loop do
prime = ch.receive
@@ -49,7 +49,7 @@ def sieve(num)
runners = []
concurrency.times do |n|
- runners << go do
+ runners << go! do
primes = sieve(n)
nth_prime.times { primes.receive }
end
@@ -6,7 +6,7 @@
# second.
Request = Struct.new(:args, :resultChan)
-clientRequests = Agent::Channel.new(name: :clientRequests, type: Request, size: 2)
+clientRequests = channel!(:type => Request, :size => 2)
# Now, we create a new worker block, which takes in a “reqs” object, calls receive on it
# (hint, req’s is a Channel!), sleeps for a bit, and then sends back a timestamped
@@ -22,15 +22,15 @@
end
# start two workers
-go(clientRequests, &worker)
-go(clientRequests, &worker)
+go!(clientRequests, &worker)
+go!(clientRequests, &worker)
# The rest is simple, we create two distinct requests, which carry a number and a reply
# channel, and pass them to our clientRequests pipe, on which our workers are waiting.
# Once dispatched, we simply call receive and wait for the results!
-req1 = Request.new(1, Agent::Channel.new(:name => :resultChan1, :type => String))
-req2 = Request.new(2, Agent::Channel.new(:name => :resultChan2, :type => String))
+req1 = Request.new(1, channel!(:type => String))
+req2 = Request.new(2, channel!(:type => String))
clientRequests << req1
clientRequests << req2
@@ -1,10 +1,11 @@
require 'lib/agent'
-c = Agent::Channel.new(name: :incr, type: Integer)
+c = channel!(:type => Integer)
-go(c) do |c, i=0|
+go!(c) do |c|
+ i = 0
loop { c << i+= 1 }
end
p c.receive # => 1
-p c.receive # => 2
+p c.receive # => 2
View
@@ -1,26 +1 @@
-require 'monitor'
-require 'thread'
-require 'securerandom'
-
-require 'agent/channel'
-require 'agent/selector'
-require 'agent/transport/queue'
-
-module Kernel
- def go(*args, &blk)
- Thread.new do
- begin
- blk.call(*args)
- rescue Exception => e
- p e
- p e.backtrace
- end
- end
- end
-
- def select(&blk)
- s = Agent::Selector.new
- yield s
- s.select
- end
-end
+require "agent/all"
View
@@ -0,0 +1,20 @@
+require "thread"
+require "monitor"
+
+require "agent/version"
+require "agent/errors"
+require "agent/error"
+require "agent/once"
+require "agent/pop"
+require "agent/push"
+require "agent/notifier"
+require "agent/uuid"
+require "agent/wait_group"
+require "agent/go"
+require "agent/queues"
+require "agent/queue"
+require "agent/channel"
+require "agent/selector"
+require "agent/kernel/channel"
+require "agent/kernel/go"
+require "agent/kernel/select"
View
@@ -1,101 +1,125 @@
-# Channels combine communication—the exchange of a value—with synchronization—guaranteeing
-# that two calculations (goroutines) are in a known state.
-# - http://golang.org/doc/effective_go.html#channels
+require "agent/uuid"
+require "agent/push"
+require "agent/pop"
+require "agent/queues"
+require "agent/errors"
module Agent
+ def self.channel!(options)
+ Agent::Channel.new(options)
+ end
+
class Channel
- attr_reader :name, :transport, :chan
+ attr_reader :name, :chan, :queue
+
+ class InvalidDirection < Exception; end
+ class Untyped < Exception; end
+ class InvalidType < Exception; end
+ class ChannelClosed < Exception; end
def initialize(opts = {})
@nate
nate Mar 20, 2012 collaborator

What do you think about this change in syntax?

# You only need the type in the case of unbuffered channels
string_unbuffered_channel = channel!(String)
# But if you want to give it a name, that could work like this:
string_unbuffered_channel_with_opts = channel!(String, :name => "foo")

# Allow easier passing of the size of a channel (more go-like in its declaration)
string_buffered_channel = channel!(String, 1)
# But still allow the other options (name)
string_buffered_channel_with_opts = channel!(String, 1, :name => "foo"))

It could be implemented like this:

def initialize(*args)
  opts = args.pop if args.last.is_a?(Hash)
  type = args.shift
  size = args.shift || 0

  raise Untyped unless type

  # Module includes both classes and modules
  raise InvalidType unless type.is_a?(Module)

  @state      = :open
  @name       = opts[:name] || Agent::UUID.generate
  @max        = size
  @type       = type
  @direction  = opts[:direction] || :bidirectional

  @close_mutex = Mutex.new

  @queue = Queues.register(@name, @max)
end
@igrigorik
igrigorik Mar 22, 2012 owner

works for me

- raise InvalidName if !opts[:name].is_a?(Symbol) || opts[:name].nil?
- raise Untyped if opts[:type].nil?
+ raise Untyped unless opts[:type]
+
+ # Module includes both classes and modules
+ raise InvalidType unless opts[:type].is_a?(Module)
- @state = :active
- @name = opts[:name]
+ @state = :open
+ @name = opts[:name] || Agent::UUID.generate
@max = opts[:size] || 1
@type = opts[:type]
@direction = opts[:direction] || :bidirectional
- @transport = opts[:transport] || Agent::Transport::Queue
- @rcb, @wcb = [], []
- @chan = @transport.new(@name, @max)
+ @queue = Queues.register(@name, @max)
end
- def marshal_load(ary)
- @state, @name, @type, @direction, @transport, @rcb, @wcb = *ary
- @chan = @transport.new(@name)
- self
- end
+ def queue
+ return @queue if @queue
- def register_callback(type, c)
- case type
- when :receive then @rcb << c
- when :send then @wcb << c
- end
+ raise ChannelClosed
end
- def remove_callback(type, name)
- case type
- when :receive then @rcb.delete_if {|c| c.chan.name == name }
- when :send then @wcb.delete_if {|c| c.chan.name == name }
- end
+
+ # Serialization methods
+
+ def marshal_load(ary)
+ @state, @name, @max, @type, @direction = *ary
+ @queue = Queues.queues[@name]
+ self
end
def marshal_dump
- [@state, @name, @type, @direction, @transport, @rcb, @wcb]
+ [@state, @name, @max, @type, @direction]
end
- def push?; @chan.push?; end
- alias :send? :push?
- def send(msg, nonblock = false)
+ # Sending methods
+
+ def send(object, options={})
check_direction(:send)
- check_type(msg)
+ check_type(object)
+
+ push = Push.new(object, options)
+ queue.push(push)
+
+ return push if options[:deferred]
- @chan.send(Marshal.dump(msg), nonblock)
- callback(:receive, @rcb.shift)
+ push.wait
end
alias :push :send
alias :<< :send
- def pop?; @chan.pop?; end
- alias :receive? :pop?
+ def push?; queue.push?; end
+ alias :send? :push?
+
+
+ # Receiving methods
- def receive(nonblock = false)
+ def receive(options={})
check_direction(:receive)
- msg = Marshal.load(@chan.receive(nonblock))
- check_type(msg)
- callback(:send, @wcb.shift)
+ pop = Pop.new(options)
+ queue.pop(pop)
- msg
+ return pop if options[:deferred]
+
+ ok = pop.wait
+ [pop.object, ok]
@nate
nate Mar 9, 2012 collaborator

I'm emulating go's channel behavior right here, but I'm not really sure if that's a good idea.

With go, if a channel gets closed while waiting to write to it, you get a panic. If some code is trying to 'receive' on a channel, and it gets closed, then you get a multi-return value like this.

I like it.

@igrigorik
igrigorik Mar 9, 2012 owner

Since we're trying to emulate Go's behavior... we should probably defer to its mechanics as much as we can. In other words, lgtm.

end
alias :pop :receive
- def closed?; @state == :closed; end
+ def pop?; queue.pop?; end
+ alias :receive? :pop?
+
+
+ # Closing methods
+
def close
- @chan.close
+ return if @state == :closed
@state = :closed
+ queue.close
+ Queues.remove(@name)
end
+ def closed?; @state == :closed; end
+ def open?; @state == :open; end
- private
+ def remove_operations(operations)
+ # ugly, but it overcomes the race condition without synchronization
+ # since instance variable access is atomic.
+ q = @queue
+ q.remove_operations(operations) if q
+ end
- def callback(type, c)
- c.send Agent::Notification.new(type, self) if c
- end
- def check_type(msg)
- raise InvalidType if !msg.is_a? @type
- end
+ private
- def check_direction(direction)
- return if @direction == :bidirectional
- raise InvalidDirection if @direction != direction
- end
+ def check_type(object)
+ raise InvalidType unless object.is_a?(@type)
+ end
+
+ def check_direction(direction)
+ return if @direction == :bidirectional
+ raise InvalidDirection if @direction != direction
+ end
- class InvalidDirection < Exception; end
- class InvalidName < Exception; end
- class Untyped < Exception; end
- class InvalidType < Exception; end
end
end
View
@@ -0,0 +1,15 @@
+module Agent
+ class Error
+ def initialize(message)
+ @message = message
+ end
+
+ def to_s
+ @message
+ end
+
+ def message?(message)
+ @message == message
+ end
+ end
+end
View
@@ -0,0 +1,3 @@
+module Agent
+ class BlockMissing < Exception; end
+end
View
@@ -0,0 +1,9 @@
+require "thread"
+require "agent/errors"
+
+module Agent
+ def self.go!(*args, &blk)
+ raise BlockMissing unless blk
+ Thread.new(*args, &blk)
+ end
+end
@@ -0,0 +1,7 @@
+require "agent/channel"
+
+module Kernel
+ def channel!(options)
+ Agent.channel!(options)
+ end
+end
@@ -0,0 +1,7 @@
+require "agent/go"
+
+module Kernel
+ def go!(*args, &blk)
+ Agent.go!(*args, &blk)
+ end
+end
@@ -0,0 +1,7 @@
+require "agent/selector"
+
+module Kernel
+ def select!(&blk)
+ Agent.select!(&blk)
+ end
+end
Oops, something went wrong. Retry.

0 comments on commit 492632f

Please sign in to comment.