garaio_bunny 2.19.1

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +231 -0
  3. data/lib/amq/protocol/extensions.rb +16 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +55 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
  7. data/lib/bunny/channel.rb +2055 -0
  8. data/lib/bunny/channel_id_allocator.rb +82 -0
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
  10. data/lib/bunny/concurrent/condition.rb +66 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +62 -0
  12. data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
  13. data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
  14. data/lib/bunny/consumer.rb +128 -0
  15. data/lib/bunny/consumer_tag_generator.rb +23 -0
  16. data/lib/bunny/consumer_work_pool.rb +122 -0
  17. data/lib/bunny/cruby/socket.rb +110 -0
  18. data/lib/bunny/cruby/ssl_socket.rb +118 -0
  19. data/lib/bunny/delivery_info.rb +93 -0
  20. data/lib/bunny/exceptions.rb +269 -0
  21. data/lib/bunny/exchange.rb +275 -0
  22. data/lib/bunny/framing.rb +56 -0
  23. data/lib/bunny/get_response.rb +83 -0
  24. data/lib/bunny/heartbeat_sender.rb +71 -0
  25. data/lib/bunny/jruby/socket.rb +57 -0
  26. data/lib/bunny/jruby/ssl_socket.rb +58 -0
  27. data/lib/bunny/message_properties.rb +119 -0
  28. data/lib/bunny/queue.rb +393 -0
  29. data/lib/bunny/reader_loop.rb +158 -0
  30. data/lib/bunny/return_info.rb +74 -0
  31. data/lib/bunny/session.rb +1483 -0
  32. data/lib/bunny/socket.rb +14 -0
  33. data/lib/bunny/ssl_socket.rb +14 -0
  34. data/lib/bunny/test_kit.rb +41 -0
  35. data/lib/bunny/timeout.rb +7 -0
  36. data/lib/bunny/transport.rb +526 -0
  37. data/lib/bunny/version.rb +6 -0
  38. data/lib/bunny/versioned_delivery_tag.rb +28 -0
  39. data/lib/bunny.rb +92 -0
  40. metadata +127 -0
@@ -0,0 +1,82 @@
1
+ require "thread"
2
+ require "monitor"
3
+ require "amq/int_allocator"
4
+
5
+ module Bunny
6
+ # Bitset-based channel id allocator. When channels are closed,
7
+ # ids are released back to the pool.
8
+ #
9
+ # Every connection has its own allocator.
10
+ #
11
+ # Allocating and releasing ids is synchronized and can be performed
12
+ # from multiple threads.
13
+ class ChannelIdAllocator
14
+
15
+ #
16
+ # API
17
+ #
18
+
19
+ # @param [Integer] max_channel Max allowed channel id
20
+ def initialize(max_channel = ((1 << 11) - 1))
21
+ # channel 0 has special meaning in the protocol, so start
22
+ # allocator at 1
23
+ @allocator = AMQ::IntAllocator.new(1, max_channel)
24
+ @mutex = Monitor.new
25
+ end
26
+
27
+
28
+ # Returns next available channel id. This method is thread safe.
29
+ #
30
+ # @return [Integer]
31
+ # @api public
32
+ # @see ChannelManager#release_channel_id
33
+ # @see ChannelManager#reset_channel_id_allocator
34
+ def next_channel_id
35
+ @mutex.synchronize do
36
+ @allocator.allocate
37
+ end
38
+ end
39
+
40
+ # Releases previously allocated channel id. This method is thread safe.
41
+ #
42
+ # @param [Integer] i Channel id to release
43
+ # @api public
44
+ # @see ChannelManager#next_channel_id
45
+ # @see ChannelManager#reset_channel_id_allocator
46
+ def release_channel_id(i)
47
+ @mutex.synchronize do
48
+ @allocator.release(i)
49
+ end
50
+ end
51
+
52
+
53
+ # Returns true if given channel id has been previously allocated and not yet released.
54
+ # This method is thread safe.
55
+ #
56
+ # @param [Integer] i Channel id to check
57
+ # @return [Boolean] true if given channel id has been previously allocated and not yet released
58
+ # @api public
59
+ # @see ChannelManager#next_channel_id
60
+ # @see ChannelManager#release_channel_id
61
+ def allocated_channel_id?(i)
62
+ @mutex.synchronize do
63
+ @allocator.allocated?(i)
64
+ end
65
+ end
66
+
67
+ # Resets channel allocator. This method is thread safe.
68
+ # @api public
69
+ # @see Channel.next_channel_id
70
+ # @see Channel.release_channel_id
71
+ def reset_channel_id_allocator
72
+ @mutex.synchronize do
73
+ @allocator.reset
74
+ end
75
+ end
76
+
77
+ # @private
78
+ def synchronize(&block)
79
+ @mutex.synchronize(&block)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,75 @@
1
+ require "set"
2
+ require "thread"
3
+ require "monitor"
4
+
5
+ module Bunny
6
+ module Concurrent
7
+ # Minimalistic implementation of a synchronized fixnum value,
8
+ # designed after (but not implementing the entire API of!)
9
+ #
10
+ # @note Designed to be intentionally minimalistic and only cover Bunny's needs.
11
+ #
12
+ # @api public
13
+ class AtomicFixnum
14
+ def initialize(n = 0)
15
+ @n = n
16
+ @mutex = Monitor.new
17
+ end
18
+
19
+ def get
20
+ @mutex.synchronize do
21
+ @n
22
+ end
23
+ end
24
+ alias to_i get
25
+
26
+ def set(n)
27
+ @mutex.synchronize do
28
+ @n = n
29
+ end
30
+ end
31
+
32
+ def increment
33
+ @mutex.synchronize do
34
+ @n = @n + 1
35
+ end
36
+ end
37
+ alias inc increment
38
+ alias increment_and_get increment
39
+
40
+ def get_and_add(i)
41
+ @mutex.synchronize do
42
+ v = @n
43
+ @n = @n + i
44
+
45
+ v
46
+ end
47
+ end
48
+
49
+ def get_and_increment
50
+ @mutex.synchronize do
51
+ v = @n
52
+ @n = @n + 1
53
+
54
+ v
55
+ end
56
+ end
57
+
58
+ def decrement
59
+ @mutex.synchronize do
60
+ @n = @n - 1
61
+ end
62
+ end
63
+ alias dec decrement
64
+ alias decrement_and_get decrement
65
+
66
+ def ==(m)
67
+ @mutex.synchronize { @n == m }
68
+ end
69
+
70
+ def ===(v)
71
+ @mutex.synchronize { @n === v }
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,66 @@
1
+ require "thread"
2
+ require "monitor"
3
+
4
+ module Bunny
5
+ # @private
6
+ module Concurrent
7
+ # Akin to java.util.concurrent.Condition and intrinsic object monitors (Object#wait, Object#notify, Object#notifyAll) in Java:
8
+ # threads can wait (block until notified) on a condition other threads notify them about.
9
+ # Unlike the j.u.c. version, this one has a single waiting set.
10
+ #
11
+ # Conditions can optionally be annotated with a description string for ease of debugging.
12
+ # @private
13
+ class Condition
14
+ attr_reader :waiting_threads, :description
15
+
16
+
17
+ def initialize(description = nil)
18
+ @mutex = Monitor.new
19
+ @waiting_threads = []
20
+ @description = description
21
+ end
22
+
23
+ def wait
24
+ @mutex.synchronize do
25
+ t = Thread.current
26
+ @waiting_threads.push(t)
27
+ end
28
+
29
+ Thread.stop
30
+ end
31
+
32
+ def notify
33
+ @mutex.synchronize do
34
+ t = @waiting_threads.shift
35
+ begin
36
+ t.run if t
37
+ rescue ThreadError
38
+ retry
39
+ end
40
+ end
41
+ end
42
+
43
+ def notify_all
44
+ @mutex.synchronize do
45
+ @waiting_threads.each do |t|
46
+ t.run
47
+ end
48
+
49
+ @waiting_threads.clear
50
+ end
51
+ end
52
+
53
+ def waiting_set_size
54
+ @mutex.synchronize { @waiting_threads.size }
55
+ end
56
+
57
+ def any_threads_waiting?
58
+ @mutex.synchronize { !@waiting_threads.empty? }
59
+ end
60
+
61
+ def none_threads_waiting?
62
+ @mutex.synchronize { @waiting_threads.empty? }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,62 @@
1
+ require "thread"
2
+
3
+ module Bunny
4
+ module Concurrent
5
+ # Continuation queue implementation for MRI and Rubinius
6
+ #
7
+ # @private
8
+ class ContinuationQueue
9
+ def initialize
10
+ @q = []
11
+ @lock = ::Mutex.new
12
+ @cond = ::ConditionVariable.new
13
+ end
14
+
15
+ def push(item)
16
+ @lock.synchronize do
17
+ @q.push(item)
18
+ @cond.signal
19
+ end
20
+ end
21
+ alias << push
22
+
23
+ def pop
24
+ poll
25
+ end
26
+
27
+ def poll(timeout_in_ms = nil)
28
+ timeout = timeout_in_ms ? timeout_in_ms / 1000.0 : nil
29
+
30
+ @lock.synchronize do
31
+ timeout_strikes_at = Time.now.utc + (timeout || 0)
32
+ while @q.empty?
33
+ wait = if timeout
34
+ timeout_strikes_at - Time.now.utc
35
+ else
36
+ nil
37
+ end
38
+ @cond.wait(@lock, wait)
39
+ raise ::Timeout::Error if wait && Time.now.utc >= timeout_strikes_at
40
+ end
41
+ item = @q.shift
42
+ item
43
+ end
44
+ end
45
+
46
+ def clear
47
+ @lock.synchronize do
48
+ @q.clear
49
+ end
50
+ end
51
+
52
+ def empty?
53
+ @q.empty?
54
+ end
55
+
56
+ def size
57
+ @q.size
58
+ end
59
+ alias length size
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,61 @@
1
+ if !defined?(JRUBY_VERSION)
2
+ raise "Bunny::Concurrent::LinkedContinuationQueue can only be used on JRuby!"
3
+ end
4
+
5
+ require "java"
6
+
7
+ java_import java.util.concurrent.LinkedBlockingQueue
8
+ java_import java.util.concurrent.TimeUnit
9
+
10
+ module Bunny
11
+ module Concurrent
12
+ # Continuation queue implementation for JRuby.
13
+ #
14
+ # On JRuby, we'd rather use reliable and heavily battle tested j.u.c.
15
+ # primitives with well described semantics than informally specified, clumsy
16
+ # and limited Ruby standard library parts.
17
+ #
18
+ # This is an implementation of the continuation queue on top of the linked blocking
19
+ # queue in j.u.c.
20
+ #
21
+ # Compared to the Ruby standard library Queue, there is one limitation: you cannot
22
+ # push a nil on the queue, it will fail with a null pointer exception.
23
+ # @private
24
+ class LinkedContinuationQueue
25
+ def initialize(*args, &block)
26
+ @q = LinkedBlockingQueue.new
27
+ end
28
+
29
+ def push(el, timeout_in_ms = nil)
30
+ if timeout_in_ms
31
+ @q.offer(el, timeout_in_ms, TimeUnit::MILLISECONDS)
32
+ else
33
+ @q.offer(el)
34
+ end
35
+ end
36
+ alias << push
37
+
38
+ def pop
39
+ @q.take
40
+ end
41
+
42
+ def poll(timeout_in_ms = nil)
43
+ if timeout_in_ms
44
+ v = @q.poll(timeout_in_ms, TimeUnit::MILLISECONDS)
45
+ raise ::Timeout::Error.new("operation did not finish in #{timeout_in_ms} ms") if v.nil?
46
+ v
47
+ else
48
+ @q.poll
49
+ end
50
+ end
51
+
52
+ def clear
53
+ @q.clear
54
+ end
55
+
56
+ def method_missing(selector, *args, &block)
57
+ @q.__send__(selector, *args, &block)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,56 @@
1
+ require "set"
2
+ require "thread"
3
+
4
+ module Bunny
5
+ module Concurrent
6
+ # A SortedSet variation that synchronizes key mutation operations.
7
+ #
8
+ # @note This is NOT a complete SortedSet replacement. It only synchronizes operations needed by Bunny.
9
+ # @api public
10
+ class SynchronizedSortedSet < SortedSet
11
+ def initialize(enum = nil)
12
+ @mutex = Mutex.new
13
+
14
+ super
15
+ end
16
+
17
+ def add(o)
18
+ # avoid using Mutex#synchronize because of a Ruby 1.8.7-specific
19
+ # bug that prevents super from being called from within a block. MK.
20
+ @mutex.lock
21
+ begin
22
+ super
23
+ ensure
24
+ @mutex.unlock
25
+ end
26
+ end
27
+
28
+ def delete(o)
29
+ @mutex.lock
30
+ begin
31
+ super
32
+ ensure
33
+ @mutex.unlock
34
+ end
35
+ end
36
+
37
+ def delete_if(&block)
38
+ @mutex.lock
39
+ begin
40
+ super
41
+ ensure
42
+ @mutex.unlock
43
+ end
44
+ end
45
+
46
+ def include?(o)
47
+ @mutex.lock
48
+ begin
49
+ super
50
+ ensure
51
+ @mutex.unlock
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,128 @@
1
+ module Bunny
2
+ # Base class that represents consumer interface. Subclasses of this class implement
3
+ # specific logic of handling consumer life cycle events. Note that when the only event
4
+ # you are interested in is message deliveries, it is recommended to just use
5
+ # {Bunny::Queue#subscribe} instead of subclassing this class.
6
+ #
7
+ # @see Bunny::Queue#subscribe
8
+ # @see Bunny::Queue#subscribe_with
9
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
10
+ # @api public
11
+ class Consumer
12
+
13
+ #
14
+ # API
15
+ #
16
+
17
+ attr_reader :channel
18
+ attr_reader :queue
19
+ attr_accessor :consumer_tag
20
+ attr_reader :arguments
21
+ attr_reader :no_ack
22
+ attr_reader :exclusive
23
+
24
+
25
+ # @param [Bunny::Channel] channel Channel this consumer will use
26
+ # @param [Bunny::Queue,String] queue Queue messages will be consumed from
27
+ # @param [String] consumer_tag Consumer tag (unique identifier). Generally it is better to let Bunny generate one.
28
+ # Empty string means RabbitMQ will generate consumer tag.
29
+ # @param [Boolean] no_ack (true) If true, delivered messages will be automatically acknowledged.
30
+ # If false, manual acknowledgements will be necessary.
31
+ # @param [Boolean] exclusive (false) Should this consumer be exclusive?
32
+ # @param [Hash] arguments (nil) Optional arguments that may be used by RabbitMQ extensions, etc
33
+ # @api public
34
+ def initialize(channel, queue, consumer_tag = channel.generate_consumer_tag, no_ack = true, exclusive = false, arguments = {})
35
+ @channel = channel || raise(ArgumentError, "channel is nil")
36
+ @queue = queue || raise(ArgumentError, "queue is nil")
37
+ @consumer_tag = consumer_tag
38
+ @exclusive = exclusive
39
+ @arguments = arguments
40
+ # no_ack set to true = no manual ack = automatic ack. MK.
41
+ @no_ack = no_ack
42
+
43
+ @on_cancellation = []
44
+ end
45
+
46
+ # Defines message delivery handler
47
+ # @api public
48
+ def on_delivery(&block)
49
+ @on_delivery = block
50
+ self
51
+ end
52
+
53
+ # Invokes message delivery handler
54
+ # @private
55
+ def call(*args)
56
+ @on_delivery.call(*args) if @on_delivery
57
+ end
58
+ alias handle_delivery call
59
+
60
+ # Defines consumer cancellation notification handler
61
+ #
62
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
63
+ # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
64
+ # @api public
65
+ def on_cancellation(&block)
66
+ @on_cancellation << block
67
+ self
68
+ end
69
+
70
+ # Invokes consumer cancellation notification handler
71
+ # @private
72
+ def handle_cancellation(basic_cancel)
73
+ @on_cancellation.each do |fn|
74
+ fn.call(basic_cancel)
75
+ end
76
+ end
77
+
78
+ # Cancels this consumer. Messages for this consumer will no longer be delivered. If the queue
79
+ # it was on is auto-deleted and this consumer was the last one, the queue will be deleted.
80
+ #
81
+ # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
82
+ # @api public
83
+ def cancel
84
+ @channel.basic_cancel(@consumer_tag)
85
+ end
86
+
87
+ # @return [String] More detailed human-readable string representation of this consumer
88
+ def inspect
89
+ "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name}> @consumer_tag=#{@consumer_tag} @exclusive=#{@exclusive} @no_ack=#{@no_ack}>"
90
+ end
91
+
92
+ # @return [String] Brief human-readable string representation of this consumer
93
+ def to_s
94
+ "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name}> @consumer_tag=#{@consumer_tag}>"
95
+ end
96
+
97
+ # @return [Boolean] true if this consumer uses automatic acknowledgement mode
98
+ # @api public
99
+ def automatic_acknowledgement?
100
+ @no_ack == true
101
+ end
102
+
103
+ # @return [Boolean] true if this consumer uses manual (explicit) acknowledgement mode
104
+ # @api public
105
+ def manual_acknowledgement?
106
+ @no_ack == false
107
+ end
108
+
109
+ # @return [String] Name of the queue this consumer is on
110
+ # @api public
111
+ def queue_name
112
+ if @queue.respond_to?(:name)
113
+ @queue.name
114
+ else
115
+ @queue
116
+ end
117
+ end
118
+
119
+ #
120
+ # Recovery
121
+ #
122
+
123
+ # @private
124
+ def recover_from_network_failure
125
+ @channel.basic_consume_with(self)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,23 @@
1
+ module Bunny
2
+ # Used to generate consumer tags in the client
3
+ class ConsumerTagGenerator
4
+
5
+ #
6
+ # API
7
+ #
8
+
9
+ # @return [String] Generated consumer tag
10
+ def generate
11
+ "#{Kernel.rand}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
12
+ end # generate
13
+
14
+
15
+ # Unique string supposed to be used as a consumer tag.
16
+ #
17
+ # @return [String] Unique string.
18
+ # @api public
19
+ def generate_prefixed(name = "bunny")
20
+ "#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,122 @@
1
+ require "thread"
2
+
3
+ module Bunny
4
+ # Thread pool that dispatches consumer deliveries. Not supposed to be shared between channels
5
+ # or threads.
6
+ #
7
+ # Every channel its own consumer pool.
8
+ #
9
+ # @private
10
+ class ConsumerWorkPool
11
+
12
+ #
13
+ # API
14
+ #
15
+
16
+ attr_reader :threads
17
+ attr_reader :size
18
+ attr_reader :abort_on_exception
19
+
20
+ def initialize(size = 1, abort_on_exception = false, shutdown_timeout = 60)
21
+ @size = size
22
+ @abort_on_exception = abort_on_exception
23
+ @shutdown_timeout = shutdown_timeout
24
+ @shutdown_mutex = ::Mutex.new
25
+ @shutdown_conditional = ::ConditionVariable.new
26
+ @queue = ::Queue.new
27
+ @paused = false
28
+ @running = false
29
+ end
30
+
31
+
32
+ def submit(callable = nil, &block)
33
+ @queue.push(callable || block)
34
+ end
35
+
36
+ def start
37
+ @threads = []
38
+
39
+ @size.times do
40
+ t = Thread.new(&method(:run_loop))
41
+ t.abort_on_exception = true if abort_on_exception
42
+ @threads << t
43
+ end
44
+
45
+ @running = true
46
+ end
47
+
48
+ def running?
49
+ @running
50
+ end
51
+
52
+ def backlog
53
+ @queue.length
54
+ end
55
+
56
+ def busy?
57
+ !@queue.empty?
58
+ end
59
+
60
+ def shutdown(wait_for_workers = false)
61
+ was_running = running?
62
+ @running = false
63
+
64
+ @size.times do
65
+ submit do |*args|
66
+ throw :terminate
67
+ end
68
+ end
69
+
70
+ return if !(wait_for_workers && @shutdown_timeout && was_running)
71
+
72
+ @shutdown_mutex.synchronize do
73
+ @shutdown_conditional.wait(@shutdown_mutex, @shutdown_timeout)
74
+ end
75
+ end
76
+
77
+ def join(timeout = nil)
78
+ (@threads || []).each { |t| t.join(timeout) }
79
+ end
80
+
81
+ def pause
82
+ @running = false
83
+ @paused = true
84
+ end
85
+
86
+ def resume
87
+ @running = true
88
+ @paused = false
89
+
90
+ @threads.each { |t| t.run }
91
+ end
92
+
93
+ def kill
94
+ @running = false
95
+
96
+ (@threads || []).each { |t| t.kill }
97
+ end
98
+
99
+ protected
100
+
101
+ def run_loop
102
+ catch(:terminate) do
103
+ loop do
104
+ Thread.stop if @paused
105
+ callable = @queue.pop
106
+
107
+ begin
108
+ callable.call
109
+ rescue ::StandardError => e
110
+ # TODO: use connection logger
111
+ $stderr.puts e.class.name
112
+ $stderr.puts e.message
113
+ end
114
+ end
115
+ end
116
+
117
+ @shutdown_mutex.synchronize do
118
+ @shutdown_conditional.signal unless busy?
119
+ end
120
+ end
121
+ end
122
+ end