agent 0.1.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rspec +0 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +12 -12
- data/README.md +51 -36
- data/Rakefile +5 -0
- data/agent.gemspec +1 -0
- data/autotest/discover.rb +9 -1
- data/benchmark/multi_ruby_bench.sh +87 -0
- data/benchmark/sieve.rb +238 -0
- data/examples/agent-workers.rb +51 -0
- data/examples/producer-consumer.rb +15 -0
- data/lib/agent.rb +1 -15
- data/lib/agent/all.rb +22 -0
- data/lib/agent/blocking_once.rb +26 -0
- data/lib/agent/channel.rb +88 -41
- data/lib/agent/error.rb +15 -0
- data/lib/agent/errors.rb +14 -0
- data/lib/agent/go.rb +9 -0
- data/lib/agent/kernel/channel.rb +7 -0
- data/lib/agent/kernel/go.rb +7 -0
- data/lib/agent/kernel/select.rb +7 -0
- data/lib/agent/notifier.rb +34 -0
- data/lib/agent/once.rb +32 -0
- data/lib/agent/pop.rb +70 -0
- data/lib/agent/push.rb +70 -0
- data/lib/agent/queue.rb +133 -0
- data/lib/agent/queue/buffered.rb +68 -0
- data/lib/agent/queue/unbuffered.rb +88 -0
- data/lib/agent/queues.rb +50 -0
- data/lib/agent/selector.rb +119 -0
- data/lib/agent/uuid.rb +36 -0
- data/lib/agent/version.rb +1 -1
- data/lib/agent/wait_group.rb +43 -0
- data/spec/blocking_once_spec.rb +122 -0
- data/spec/channel_spec.rb +153 -82
- data/spec/error_spec.rb +15 -0
- data/spec/examples/channel_of_channels_spec.rb +17 -14
- data/spec/examples/producer_consumer_spec.rb +26 -16
- data/spec/examples/sieve_spec.rb +43 -37
- data/spec/go_spec.rb +17 -0
- data/spec/notifier_spec.rb +42 -0
- data/spec/once_spec.rb +91 -0
- data/spec/pop_spec.rb +97 -0
- data/spec/push_spec.rb +95 -0
- data/spec/queue_spec.rb +335 -37
- data/spec/queues_spec.rb +28 -0
- data/spec/selector_spec.rb +398 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/uuid_spec.rb +13 -0
- data/spec/wait_group_spec.rb +45 -0
- metadata +94 -54
- data/lib/agent/transport/queue.rb +0 -82
- 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
|
data/lib/agent.rb
CHANGED
data/lib/agent/all.rb
ADDED
@@ -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
|
data/lib/agent/channel.rb
CHANGED
@@ -1,74 +1,121 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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, :
|
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
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
21
|
-
end
|
33
|
+
# Serialization methods
|
22
34
|
|
23
35
|
def marshal_load(ary)
|
24
|
-
@
|
25
|
-
@
|
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
|
-
[@
|
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
|
-
|
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
|
43
|
-
|
56
|
+
def push?; queue.push?; end
|
57
|
+
alias :send? :push?
|
58
|
+
|
44
59
|
|
45
|
-
|
46
|
-
check_type(msg)
|
60
|
+
# Receiving methods
|
47
61
|
|
48
|
-
|
62
|
+
def receive(options={})
|
63
|
+
check_direction(:receive)
|
64
|
+
queue.pop(options)
|
49
65
|
end
|
50
66
|
alias :pop :receive
|
51
67
|
|
52
|
-
def
|
68
|
+
def pop?; queue.pop?; end
|
69
|
+
alias :receive? :pop?
|
70
|
+
|
71
|
+
|
72
|
+
# Closing methods
|
73
|
+
|
53
74
|
def close
|
54
|
-
@
|
55
|
-
|
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
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
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
|
data/lib/agent/error.rb
ADDED
data/lib/agent/errors.rb
ADDED
@@ -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
|
data/lib/agent/go.rb
ADDED
@@ -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
|
data/lib/agent/once.rb
ADDED
@@ -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
|
data/lib/agent/pop.rb
ADDED
@@ -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
|