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,51 @@
1
+ project_lib_path = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
2
+ $LOAD_PATH.unshift(project_lib_path)
3
+ require 'agent'
4
+
5
+ # First, we declare a new Ruby struct, which will encapsulate several arguments, and then
6
+ # declare a clientRequests channel, which will carry our Request struct. Nothing unusual,
7
+ # except that we also set the size of our channel to two – we’ll see why in a
8
+ # second.
9
+
10
+ Request = Struct.new(:args, :resultChan)
11
+ clientRequests = channel!(Request, 2)
12
+
13
+ # Now, we create a new worker block, which takes in a “reqs” object, calls receive on it
14
+ # (hint, req’s is a Channel!), sleeps for a bit, and then sends back a timestamped
15
+ # result. With the help of some Ruby syntax sugar, we then start two workers by passing
16
+ # this block to our go function.
17
+
18
+ worker = Proc.new do |reqs|
19
+ loop do
20
+ req = reqs.receive[0]
21
+ sleep 1.0
22
+ req.resultChan << [Time.now, req.args + 1].join(' : ')
23
+ end
24
+ end
25
+
26
+ # start two workers
27
+ go!(clientRequests, &worker)
28
+ go!(clientRequests, &worker)
29
+
30
+ # The rest is simple, we create two distinct requests, which carry a number and a reply
31
+ # channel, and pass them to our clientRequests pipe, on which our workers are waiting.
32
+ # Once dispatched, we simply call receive and wait for the results!
33
+
34
+ req1 = Request.new(1, channel!(String))
35
+ req2 = Request.new(2, channel!(String))
36
+
37
+ clientRequests << req1
38
+ clientRequests << req2
39
+
40
+ # retrieve results
41
+ puts req1.resultChan.receive[0] # => 2010-11-28 23:31:08 -0500 : 2
42
+ puts req2.resultChan.receive[0] # => 2010-11-28 23:31:08 -0500 : 3
43
+
44
+ # Notice something interesting? Both results came back with the same timestamp! Our
45
+ # clientRequests channel allowed for up to two messages in the pipe, which our workers
46
+ # immediately received, executed, and returned the results. Once again, not a thread
47
+ # or a mutex in sight.
48
+
49
+ clientRequests.close
50
+ req1.resultChan.close
51
+ req2.resultChan.close
@@ -0,0 +1,15 @@
1
+ project_lib_path = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
2
+ $LOAD_PATH.unshift(project_lib_path)
3
+ require 'agent'
4
+
5
+ c = channel!(Integer)
6
+
7
+ go!(c) do |c|
8
+ i = 0
9
+ loop { c << i+= 1 }
10
+ end
11
+
12
+ p c.receive[0] # => 1
13
+ p c.receive[0] # => 2
14
+
15
+ c.close
@@ -1,15 +1 @@
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
1
+ require "agent/all"
@@ -0,0 +1,22 @@
1
+ require "thread"
2
+
3
+ require "agent/version"
4
+ require "agent/errors"
5
+ require "agent/error"
6
+ require "agent/once"
7
+ require "agent/blocking_once"
8
+ require "agent/pop"
9
+ require "agent/push"
10
+ require "agent/notifier"
11
+ require "agent/uuid"
12
+ require "agent/wait_group"
13
+ require "agent/go"
14
+ require "agent/queues"
15
+ require "agent/queue"
16
+ require "agent/queue/buffered"
17
+ require "agent/queue/unbuffered"
18
+ require "agent/channel"
19
+ require "agent/selector"
20
+ require "agent/kernel/channel"
21
+ require "agent/kernel/go"
22
+ require "agent/kernel/select"
@@ -0,0 +1,26 @@
1
+ require "agent/errors"
2
+
3
+ module Agent
4
+ class BlockingOnce < Once
5
+ def perform
6
+ @mutex.synchronize do
7
+ return nil, error if @performed
8
+
9
+ begin
10
+ value = yield
11
+ @performed = true
12
+ return value, nil
13
+ rescue Errors::Rollback
14
+ return nil, rollback_error
15
+ end
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ def rollback_error
22
+ @rollback_error ||= Error.new("rolled back")
23
+ end
24
+
25
+ end
26
+ end
@@ -1,74 +1,121 @@
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
1
+ require "agent/uuid"
2
+ require "agent/push"
3
+ require "agent/pop"
4
+ require "agent/queues"
5
+ require "agent/errors"
4
6
 
5
7
  module Agent
8
+ def self.channel!(*args)
9
+ Channel.new(*args)
10
+ end
11
+
6
12
  class Channel
7
- attr_reader :name, :transport, :chan
13
+ attr_reader :name, :direction, :type, :max
14
+
15
+ def initialize(*args)
16
+ opts = args.last.is_a?(Hash) ? args.pop : {}
17
+ @type = args.shift
18
+ @max = args.shift || 0
19
+ @closed = false
20
+ @name = opts[:name] || UUID.generate
21
+ @direction = opts[:direction] || :bidirectional
22
+ @close_mutex = Mutex.new
23
+ @queue = Queues.register(@name, @type, @max)
24
+ end
8
25
 
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
26
+ def queue
27
+ q = @queue
28
+ raise Errors::ChannelClosed unless q
29
+ q
30
+ end
16
31
 
17
- raise NoName if @name.nil?
18
- raise Untyped if @type.nil?
19
32
 
20
- @chan = @transport.new(@name, @max)
21
- end
33
+ # Serialization methods
22
34
 
23
35
  def marshal_load(ary)
24
- @state, @name, @type, @direction, @transport = *ary
25
- @chan = @transport.new(@name)
36
+ @closed, @name, @max, @type, @direction = *ary
37
+ @queue = Queues[@name]
38
+ @closed = @queue.nil? || @queue.closed?
26
39
  self
27
40
  end
28
41
 
29
42
  def marshal_dump
30
- [@state, @name, @type, @direction, @transport]
43
+ [@closed, @name, @max, @type, @direction]
31
44
  end
32
45
 
33
- def send(msg)
34
- check_direction(:send)
35
- check_type(msg)
36
46
 
37
- @chan.send(Marshal.dump(msg))
47
+ # Sending methods
48
+
49
+ def send(object, options={})
50
+ check_direction(:send)
51
+ queue.push(object, options)
38
52
  end
39
53
  alias :push :send
40
54
  alias :<< :send
41
55
 
42
- def receive
43
- check_direction(:receive)
56
+ def push?; queue.push?; end
57
+ alias :send? :push?
58
+
44
59
 
45
- msg = Marshal.load(@chan.receive)
46
- check_type(msg)
60
+ # Receiving methods
47
61
 
48
- msg
62
+ def receive(options={})
63
+ check_direction(:receive)
64
+ queue.pop(options)
49
65
  end
50
66
  alias :pop :receive
51
67
 
52
- def closed?; @state == :closed; end
68
+ def pop?; queue.pop?; end
69
+ alias :receive? :pop?
70
+
71
+
72
+ # Closing methods
73
+
53
74
  def close
54
- @chan.close
55
- @state = :closed
75
+ @close_mutex.synchronize do
76
+ raise Errors::ChannelClosed if @closed
77
+ @closed = true
78
+ @queue.close
79
+ @queue = nil
80
+ Queues.delete(@name)
81
+ end
82
+ end
83
+ def closed?; @closed; end
84
+ def open?; !@closed; end
85
+
86
+ def remove_operations(operations)
87
+ # ugly, but it overcomes the race condition without synchronization
88
+ # since instance variable access is atomic.
89
+ q = @queue
90
+ q.remove_operations(operations) if q
56
91
  end
57
92
 
58
- private
93
+ def as_send_only
94
+ as_direction_only(:send)
95
+ end
96
+
97
+ def as_receive_only
98
+ as_direction_only(:receive)
99
+ end
59
100
 
60
- def check_type(msg)
61
- raise InvalidType if !msg.is_a? @type
62
- end
63
101
 
64
- def check_direction(direction)
65
- return if @direction == :bidirectional
66
- raise InvalidDirection if @direction != direction
102
+ private
103
+
104
+ def as_direction_only(direction)
105
+ @close_mutex.synchronize do
106
+ raise Errors::ChannelClosed if @closed
107
+ channel!(@type, @max, :name => @name, :direction => direction)
67
108
  end
109
+ end
110
+
111
+ def check_type(object)
112
+ raise Errors::InvalidType unless object.is_a?(@type)
113
+ end
114
+
115
+ def check_direction(direction)
116
+ return if @direction == :bidirectional
117
+ raise Errors::InvalidDirection if @direction != direction
118
+ end
68
119
 
69
- class InvalidDirection < Exception; end
70
- class NoName < Exception; end
71
- class Untyped < Exception; end
72
- class InvalidType < Exception; end
73
120
  end
74
121
  end
@@ -0,0 +1,15 @@
1
+ module Agent
2
+ class Error
3
+ def initialize(message)
4
+ @message = message
5
+ end
6
+
7
+ def to_s
8
+ @message
9
+ end
10
+
11
+ def message?(message)
12
+ @message == message
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Agent
2
+ module Errors
3
+ class BlockMissing < Exception; end
4
+ class ChannelClosed < Exception; end
5
+ class InvalidDirection < Exception; end
6
+ class Untyped < Exception; end
7
+ class InvalidType < Exception; end
8
+ class Rollback < Exception; end
9
+ class InvalidQueueSize < Exception; end
10
+ class NotImplementedError < Exception; end
11
+ class DefaultCaseAlreadyDefinedError < Exception; end
12
+ class NegativeWaitGroupCount < Exception; end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ require "thread"
2
+ require "agent/errors"
3
+
4
+ module Agent
5
+ def self.go!(*args)
6
+ raise Errors::BlockMissing unless block_given?
7
+ Thread.new(*args){|*a| yield(*a) }
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ require "agent/channel"
2
+
3
+ module Kernel
4
+ def channel!(*args)
5
+ Agent.channel!(*args)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require "agent/go"
2
+
3
+ module Kernel
4
+ def go!(*args, &blk)
5
+ Agent.go!(*args, &blk)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require "agent/selector"
2
+
3
+ module Kernel
4
+ def select!(&blk)
5
+ Agent.select!(&blk)
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ module Agent
2
+ class Notifier
3
+ attr_reader :payload
4
+
5
+ def initialize
6
+ @mutex = Mutex.new
7
+ @cvar = ConditionVariable.new
8
+ @notified = false
9
+ @payload = nil
10
+ end
11
+
12
+ def notified?
13
+ @notified
14
+ end
15
+
16
+ def wait
17
+ @mutex.synchronize do
18
+ until notified?
19
+ @cvar.wait(@mutex)
20
+ end
21
+ end
22
+ end
23
+
24
+ def notify(payload)
25
+ @mutex.synchronize do
26
+ return Error.new("already notified") if notified?
27
+ @payload = payload
28
+ @notified = true
29
+ @cvar.signal
30
+ return nil
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module Agent
2
+ class Once
3
+ def initialize
4
+ @mutex = Mutex.new
5
+ @performed = false
6
+ end
7
+
8
+ def perform
9
+ # optimium path
10
+ return nil, error if @performed
11
+
12
+ # slow path
13
+ @mutex.synchronize do
14
+ return nil, error if @performed
15
+ @performed = true
16
+ end
17
+
18
+ return yield, nil
19
+ end
20
+
21
+ def performed?
22
+ @performed
23
+ end
24
+
25
+ protected
26
+
27
+ def error
28
+ @error ||= Error.new("already performed")
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,70 @@
1
+ require "agent/errors"
2
+
3
+ module Agent
4
+ class Pop
5
+ attr_reader :uuid, :blocking_once, :notifier, :object
6
+
7
+ def initialize(options={})
8
+ @object = nil
9
+ @uuid = options[:uuid] || UUID.generate
10
+ @blocking_once = options[:blocking_once]
11
+ @notifier = options[:notifier]
12
+ @mutex = Mutex.new
13
+ @cvar = ConditionVariable.new
14
+ @received = false
15
+ @closed = false
16
+ end
17
+
18
+ def received?
19
+ @received
20
+ end
21
+
22
+ def closed?
23
+ @closed
24
+ end
25
+
26
+ def wait
27
+ @mutex.synchronize do
28
+ until @received || @closed
29
+ @cvar.wait(@mutex)
30
+ end
31
+ return received?
32
+ end
33
+ end
34
+
35
+ def send
36
+ @mutex.synchronize do
37
+ raise Errors::ChannelClosed if @closed
38
+
39
+ if @blocking_once
40
+ value, error = @blocking_once.perform do
41
+ @object = Marshal.load(yield)
42
+ @received = true
43
+ @cvar.signal
44
+ @notifier.notify(self) if @notifier
45
+ end
46
+
47
+ return error
48
+ else
49
+ begin
50
+ @object = Marshal.load(yield)
51
+ @received = true
52
+ @cvar.signal
53
+ @notifier.notify(self) if @notifier
54
+ rescue Errors::Rollback
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def close
61
+ @mutex.synchronize do
62
+ return if @received
63
+ @closed = true
64
+ @cvar.broadcast
65
+ @notifier.notify(self) if @notifier
66
+ end
67
+ end
68
+
69
+ end
70
+ end