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.
- 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
|