agent 0.1.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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,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
@@ -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
@@ -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