garaio_bunny 2.19.1

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