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
data/lib/agent/push.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require "agent/errors"
|
2
|
+
|
3
|
+
module Agent
|
4
|
+
class Push
|
5
|
+
attr_reader :object, :uuid, :blocking_once, :notifier
|
6
|
+
|
7
|
+
def initialize(object, options={})
|
8
|
+
@object = Marshal.dump(object)
|
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
|
+
@sent = false
|
15
|
+
@closed = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def sent?
|
19
|
+
@sent
|
20
|
+
end
|
21
|
+
|
22
|
+
def closed?
|
23
|
+
@closed
|
24
|
+
end
|
25
|
+
|
26
|
+
def wait
|
27
|
+
@mutex.synchronize do
|
28
|
+
until @sent || @closed
|
29
|
+
@cvar.wait(@mutex)
|
30
|
+
end
|
31
|
+
raise Errors::ChannelClosed if @closed
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def receive
|
36
|
+
@mutex.synchronize do
|
37
|
+
raise Errors::ChannelClosed if @closed
|
38
|
+
|
39
|
+
if @blocking_once
|
40
|
+
value, error = @blocking_once.perform do
|
41
|
+
yield @object
|
42
|
+
@sent = true
|
43
|
+
@cvar.signal
|
44
|
+
@notifier.notify(self) if @notifier
|
45
|
+
end
|
46
|
+
|
47
|
+
return error
|
48
|
+
else
|
49
|
+
begin
|
50
|
+
yield @object
|
51
|
+
@sent = 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 @sent
|
63
|
+
@closed = true
|
64
|
+
@cvar.broadcast
|
65
|
+
@notifier.notify(self) if @notifier
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/agent/queue.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require "agent/queue/buffered"
|
2
|
+
require "agent/queue/unbuffered"
|
3
|
+
require "agent/errors"
|
4
|
+
|
5
|
+
module Agent
|
6
|
+
class Queue
|
7
|
+
attr_reader :type, :queue, :operations, :pushes, :pops, :mutex
|
8
|
+
|
9
|
+
def initialize(type)
|
10
|
+
@type = type
|
11
|
+
|
12
|
+
raise Errors::Untyped unless @type
|
13
|
+
raise Errors::InvalidType unless @type.is_a?(Module)
|
14
|
+
|
15
|
+
@closed = false
|
16
|
+
|
17
|
+
@queue = []
|
18
|
+
@operations = []
|
19
|
+
@pushes = []
|
20
|
+
@pops = []
|
21
|
+
|
22
|
+
@mutex = Mutex.new
|
23
|
+
|
24
|
+
reset_custom_state
|
25
|
+
end
|
26
|
+
|
27
|
+
def buffered?
|
28
|
+
# implement in subclass
|
29
|
+
raise Errors::NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
def unbuffered?
|
33
|
+
# implement in subclass
|
34
|
+
raise Errors::NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
def pop?
|
38
|
+
# implement in subclass
|
39
|
+
raise Errors::NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
def push?
|
43
|
+
# implement in subclass
|
44
|
+
raise Errors::NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
mutex.synchronize do
|
49
|
+
raise Errors::ChannelClosed if @closed
|
50
|
+
@closed = true
|
51
|
+
@operations.each{|o| o.close }
|
52
|
+
@operations.clear
|
53
|
+
@queue.clear
|
54
|
+
@pushes.clear
|
55
|
+
@pops.clear
|
56
|
+
|
57
|
+
reset_custom_state
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def closed?; @closed; end
|
62
|
+
def open?; !@closed; end
|
63
|
+
|
64
|
+
def push(object, options={})
|
65
|
+
raise Errors::InvalidType unless object.is_a?(@type)
|
66
|
+
|
67
|
+
push = Push.new(object, options)
|
68
|
+
|
69
|
+
mutex.synchronize do
|
70
|
+
raise Errors::ChannelClosed if @closed
|
71
|
+
operations << push
|
72
|
+
pushes << push
|
73
|
+
process
|
74
|
+
end
|
75
|
+
|
76
|
+
return push if options[:deferred]
|
77
|
+
|
78
|
+
push.wait
|
79
|
+
end
|
80
|
+
|
81
|
+
def pop(options={})
|
82
|
+
pop = Pop.new(options)
|
83
|
+
|
84
|
+
mutex.synchronize do
|
85
|
+
raise Errors::ChannelClosed if @closed
|
86
|
+
operations << pop
|
87
|
+
pops << pop
|
88
|
+
process
|
89
|
+
end
|
90
|
+
|
91
|
+
return pop if options[:deferred]
|
92
|
+
|
93
|
+
ok = pop.wait
|
94
|
+
[pop.object, ok]
|
95
|
+
end
|
96
|
+
|
97
|
+
def remove_operations(ops)
|
98
|
+
mutex.synchronize do
|
99
|
+
return if @closed
|
100
|
+
|
101
|
+
ops.each do |operation|
|
102
|
+
operations.delete(operation)
|
103
|
+
end
|
104
|
+
|
105
|
+
pushes.clear
|
106
|
+
pops.clear
|
107
|
+
|
108
|
+
operations.each do |operation|
|
109
|
+
if operation.is_a?(Push)
|
110
|
+
pushes << operation
|
111
|
+
else
|
112
|
+
pops << operation
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
reset_custom_state
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
protected
|
122
|
+
|
123
|
+
def reset_custom_state
|
124
|
+
# implement in subclass...or not...
|
125
|
+
end
|
126
|
+
|
127
|
+
def process
|
128
|
+
# implement in subclass
|
129
|
+
raise Errors::NotImplementedError
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "agent/queue"
|
2
|
+
require "agent/errors"
|
3
|
+
|
4
|
+
module Agent
|
5
|
+
class Queue
|
6
|
+
class Buffered < Queue
|
7
|
+
attr_reader :size, :max
|
8
|
+
|
9
|
+
def initialize(type, max=1)
|
10
|
+
raise Errors::InvalidQueueSize, "queue size must be at least 1" unless max >= 1
|
11
|
+
super(type)
|
12
|
+
@max = max
|
13
|
+
end
|
14
|
+
|
15
|
+
def buffered?; true; end
|
16
|
+
def unbuffered?; false; end
|
17
|
+
|
18
|
+
def push?; @max > @size; end
|
19
|
+
def pop?; @size > 0; end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def reset_custom_state
|
24
|
+
@size = @queue.size
|
25
|
+
end
|
26
|
+
|
27
|
+
def process
|
28
|
+
return if (pops.empty? && !push?) || (pushes.empty? && !pop?)
|
29
|
+
|
30
|
+
operation = operations.first
|
31
|
+
|
32
|
+
loop do
|
33
|
+
if operation.is_a?(Push)
|
34
|
+
if push?
|
35
|
+
operation.receive do |obj|
|
36
|
+
@size += 1
|
37
|
+
queue.push(obj)
|
38
|
+
end
|
39
|
+
operations.delete(operation)
|
40
|
+
pushes.delete(operation)
|
41
|
+
elsif pop? && operation = pops[0]
|
42
|
+
next
|
43
|
+
else
|
44
|
+
break
|
45
|
+
end
|
46
|
+
else # Pop
|
47
|
+
if pop?
|
48
|
+
operation.send do
|
49
|
+
@size -= 1
|
50
|
+
queue.shift
|
51
|
+
end
|
52
|
+
operations.delete(operation)
|
53
|
+
pops.delete(operation)
|
54
|
+
elsif push? && operation = pushes[0]
|
55
|
+
next
|
56
|
+
else
|
57
|
+
break
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
operation = operations[0]
|
62
|
+
break unless operation
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require "agent/queue"
|
2
|
+
require "agent/errors"
|
3
|
+
|
4
|
+
module Agent
|
5
|
+
class Queue
|
6
|
+
class Unbuffered < Queue
|
7
|
+
|
8
|
+
attr_reader :waiting_pushes, :waiting_pops
|
9
|
+
|
10
|
+
def buffered?; false; end
|
11
|
+
def unbuffered?; true; end
|
12
|
+
|
13
|
+
def push?; @waiting_pops > 0; end
|
14
|
+
def pop?; @waiting_pushes > 0; end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def reset_custom_state
|
19
|
+
@waiting_pushes = pushes.size
|
20
|
+
@waiting_pops = pops.size
|
21
|
+
end
|
22
|
+
|
23
|
+
def process
|
24
|
+
operation = operations.last
|
25
|
+
|
26
|
+
if operation.is_a?(Push)
|
27
|
+
@waiting_pushes += 1
|
28
|
+
|
29
|
+
pops.dup.each do |pop_operation|
|
30
|
+
if operation.blocking_once && operation.blocking_once == pop_operation.blocking_once
|
31
|
+
next
|
32
|
+
end
|
33
|
+
|
34
|
+
error = operation.receive do |value|
|
35
|
+
error = pop_operation.send do
|
36
|
+
value
|
37
|
+
end
|
38
|
+
|
39
|
+
@waiting_pops -= 1
|
40
|
+
operations.delete(pop_operation)
|
41
|
+
pops.delete(pop_operation)
|
42
|
+
raise Errors::Rollback if error
|
43
|
+
end
|
44
|
+
|
45
|
+
if error.nil? || error.message?("already performed")
|
46
|
+
@waiting_pushes -= 1
|
47
|
+
operations.pop
|
48
|
+
pushes.pop
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else # Pop
|
53
|
+
@waiting_pops += 1
|
54
|
+
|
55
|
+
pushes.dup.each do |push_operation|
|
56
|
+
if operation.blocking_once && operation.blocking_once == push_operation.blocking_once
|
57
|
+
next
|
58
|
+
end
|
59
|
+
|
60
|
+
error = operation.send do
|
61
|
+
value = nil
|
62
|
+
|
63
|
+
error = push_operation.receive do |v|
|
64
|
+
value = v
|
65
|
+
end
|
66
|
+
|
67
|
+
@waiting_pushes -= 1
|
68
|
+
operations.delete(push_operation)
|
69
|
+
pushes.delete(push_operation)
|
70
|
+
raise Errors::Rollback if error
|
71
|
+
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
75
|
+
if error.nil? || error.message?("already performed")
|
76
|
+
@waiting_pops -= 1
|
77
|
+
operations.pop
|
78
|
+
pops.pop
|
79
|
+
break
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/agent/queues.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "agent/errors"
|
2
|
+
|
3
|
+
module Agent
|
4
|
+
module Queues
|
5
|
+
LOCK = Mutex.new
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :queues
|
9
|
+
end
|
10
|
+
|
11
|
+
self.queues = {}
|
12
|
+
|
13
|
+
def self.register(name, type, max)
|
14
|
+
raise Errors::Untyped unless type
|
15
|
+
raise Errors::InvalidType unless type.is_a?(Module)
|
16
|
+
|
17
|
+
LOCK.synchronize do
|
18
|
+
queue = queues[name]
|
19
|
+
|
20
|
+
if queue
|
21
|
+
if queue.type == type
|
22
|
+
return queue
|
23
|
+
else
|
24
|
+
raise Errors::InvalidType, "Type #{type.name} is different than the queue's type (#{queue.type.name})"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
raise Errors::InvalidQueueSize, "queue size must be at least 0" unless max >= 0
|
29
|
+
|
30
|
+
if max > 0
|
31
|
+
queues[name] = Queue::Buffered.new(type, max)
|
32
|
+
else
|
33
|
+
queues[name] = Queue::Unbuffered.new(type)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.delete(name)
|
39
|
+
LOCK.synchronize{ queues.delete(name) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.[](name)
|
43
|
+
LOCK.synchronize{ queues[name] }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.clear
|
47
|
+
LOCK.synchronize{ queues.clear }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require "agent/uuid"
|
2
|
+
require "agent/push"
|
3
|
+
require "agent/pop"
|
4
|
+
require "agent/channel"
|
5
|
+
require "agent/notifier"
|
6
|
+
require "agent/errors"
|
7
|
+
|
8
|
+
module Agent
|
9
|
+
def self.select!
|
10
|
+
raise Errors::BlockMissing unless block_given?
|
11
|
+
selector = Selector.new
|
12
|
+
yield selector
|
13
|
+
selector.select
|
14
|
+
ensure
|
15
|
+
if selector
|
16
|
+
selector.close_default_channel
|
17
|
+
selector.dequeue_operations
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Selector
|
22
|
+
attr_reader :cases
|
23
|
+
|
24
|
+
Case = Struct.new(:uuid, :channel, :direction, :value, :blk)
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@ordered_cases = []
|
28
|
+
@cases = {}
|
29
|
+
@operations = {}
|
30
|
+
@blocking_once = BlockingOnce.new
|
31
|
+
@notifier = Notifier.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def default(&blk)
|
35
|
+
if @default_case
|
36
|
+
raise Errors::DefaultCaseAlreadyDefinedError
|
37
|
+
else
|
38
|
+
@default_case = self.case(channel!(TrueClass, 1), :receive, &blk)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def timeout(t, &blk)
|
43
|
+
s = channel!(TrueClass, 1)
|
44
|
+
go!{ sleep t; s.send(true); s.close }
|
45
|
+
add_case(s, :timeout, &blk)
|
46
|
+
end
|
47
|
+
|
48
|
+
def case(chan, direction, value=nil, &blk)
|
49
|
+
raise "invalid case, must be a channel" unless chan.is_a?(Channel)
|
50
|
+
raise Errors::BlockMissing if blk.nil? && direction == :receive
|
51
|
+
raise Errors::InvalidDirection if direction != :send && direction != :receive
|
52
|
+
add_case(chan, direction, value, &blk)
|
53
|
+
end
|
54
|
+
|
55
|
+
def select
|
56
|
+
if !@ordered_cases.empty?
|
57
|
+
@ordered_cases.each do |cse|
|
58
|
+
if cse.direction == :send
|
59
|
+
@operations[cse.channel] << cse.channel.send(cse.value, :uuid => cse.uuid,
|
60
|
+
:blocking_once => @blocking_once,
|
61
|
+
:notifier => @notifier,
|
62
|
+
:deferred => true)
|
63
|
+
else # :receive || :timeout
|
64
|
+
@operations[cse.channel] << cse.channel.receive(:uuid => cse.uuid,
|
65
|
+
:blocking_once => @blocking_once,
|
66
|
+
:notifier => @notifier,
|
67
|
+
:deferred => true)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if @default_case
|
72
|
+
@default_case.channel.send(true, :uuid => @default_case.uuid, :blocking_once => @blocking_once, :notifier => @notifier, :deferred => true)
|
73
|
+
end
|
74
|
+
|
75
|
+
@notifier.wait
|
76
|
+
|
77
|
+
execute_case(@notifier.payload)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def dequeue_operations
|
82
|
+
@operations.each do |channel, operations|
|
83
|
+
channel.remove_operations(operations)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def close_default_channel
|
88
|
+
@default_case.channel.close if @default_case
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
def add_case(chan, direction, value=nil, &blk)
|
95
|
+
uuid = UUID.generate
|
96
|
+
cse = Case.new(uuid, chan, direction, value, blk)
|
97
|
+
@ordered_cases << cse
|
98
|
+
@cases[uuid] = cse
|
99
|
+
@operations[chan] = []
|
100
|
+
cse
|
101
|
+
end
|
102
|
+
|
103
|
+
def execute_case(operation)
|
104
|
+
raise Errors::ChannelClosed if operation.closed?
|
105
|
+
|
106
|
+
cse = @cases[operation.uuid]
|
107
|
+
blk, direction = cse.blk, cse.direction
|
108
|
+
|
109
|
+
if blk
|
110
|
+
if direction == :send || direction == :timeout
|
111
|
+
blk.call
|
112
|
+
else # :receive
|
113
|
+
blk.call(operation.object)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|