garaio_bunny 2.19.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +231 -0
- data/lib/amq/protocol/extensions.rb +16 -0
- data/lib/bunny/authentication/credentials_encoder.rb +55 -0
- data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
- data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
- data/lib/bunny/channel.rb +2055 -0
- data/lib/bunny/channel_id_allocator.rb +82 -0
- data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
- data/lib/bunny/concurrent/condition.rb +66 -0
- data/lib/bunny/concurrent/continuation_queue.rb +62 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
- data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
- data/lib/bunny/consumer.rb +128 -0
- data/lib/bunny/consumer_tag_generator.rb +23 -0
- data/lib/bunny/consumer_work_pool.rb +122 -0
- data/lib/bunny/cruby/socket.rb +110 -0
- data/lib/bunny/cruby/ssl_socket.rb +118 -0
- data/lib/bunny/delivery_info.rb +93 -0
- data/lib/bunny/exceptions.rb +269 -0
- data/lib/bunny/exchange.rb +275 -0
- data/lib/bunny/framing.rb +56 -0
- data/lib/bunny/get_response.rb +83 -0
- data/lib/bunny/heartbeat_sender.rb +71 -0
- data/lib/bunny/jruby/socket.rb +57 -0
- data/lib/bunny/jruby/ssl_socket.rb +58 -0
- data/lib/bunny/message_properties.rb +119 -0
- data/lib/bunny/queue.rb +393 -0
- data/lib/bunny/reader_loop.rb +158 -0
- data/lib/bunny/return_info.rb +74 -0
- data/lib/bunny/session.rb +1483 -0
- data/lib/bunny/socket.rb +14 -0
- data/lib/bunny/ssl_socket.rb +14 -0
- data/lib/bunny/test_kit.rb +41 -0
- data/lib/bunny/timeout.rb +7 -0
- data/lib/bunny/transport.rb +526 -0
- data/lib/bunny/version.rb +6 -0
- data/lib/bunny/versioned_delivery_tag.rb +28 -0
- data/lib/bunny.rb +92 -0
- 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
|