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