bunny 1.0.7 → 2.24.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 (168) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +92 -87
  3. data/lib/amq/protocol/extensions.rb +2 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +2 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +2 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
  7. data/lib/bunny/channel.rb +485 -186
  8. data/lib/bunny/channel_id_allocator.rb +8 -4
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +2 -0
  10. data/lib/bunny/concurrent/condition.rb +2 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +37 -13
  12. data/lib/bunny/concurrent/synchronized_sorted_set.rb +2 -0
  13. data/lib/bunny/consumer.rb +20 -13
  14. data/lib/bunny/consumer_tag_generator.rb +6 -2
  15. data/lib/bunny/consumer_work_pool.rb +37 -7
  16. data/lib/bunny/cruby/socket.rb +51 -22
  17. data/lib/bunny/cruby/ssl_socket.rb +68 -5
  18. data/lib/bunny/delivery_info.rb +3 -1
  19. data/lib/bunny/exceptions.rb +27 -4
  20. data/lib/bunny/exchange.rb +35 -29
  21. data/lib/bunny/framing.rb +2 -0
  22. data/lib/bunny/get_response.rb +85 -0
  23. data/lib/bunny/heartbeat_sender.rb +9 -6
  24. data/lib/bunny/message_properties.rb +2 -0
  25. data/lib/bunny/queue.rb +89 -41
  26. data/lib/bunny/reader_loop.rb +72 -28
  27. data/lib/bunny/return_info.rb +2 -0
  28. data/lib/bunny/session.rb +621 -225
  29. data/lib/bunny/socket.rb +7 -12
  30. data/lib/bunny/ssl_socket.rb +7 -12
  31. data/lib/bunny/test_kit.rb +15 -0
  32. data/lib/bunny/timeout.rb +3 -12
  33. data/lib/bunny/timestamp.rb +24 -0
  34. data/lib/bunny/transport.rb +223 -98
  35. data/lib/bunny/version.rb +2 -1
  36. data/lib/bunny/versioned_delivery_tag.rb +2 -0
  37. data/lib/bunny.rb +54 -8
  38. metadata +38 -224
  39. data/.gitignore +0 -22
  40. data/.rspec +0 -3
  41. data/.ruby-version +0 -1
  42. data/.travis.yml +0 -23
  43. data/.yardopts +0 -8
  44. data/ChangeLog.md +0 -1092
  45. data/Gemfile +0 -54
  46. data/LICENSE +0 -21
  47. data/benchmarks/basic_publish/with_128K_messages.rb +0 -35
  48. data/benchmarks/basic_publish/with_1k_messages.rb +0 -35
  49. data/benchmarks/basic_publish/with_4K_messages.rb +0 -35
  50. data/benchmarks/basic_publish/with_64K_messages.rb +0 -35
  51. data/benchmarks/channel_open.rb +0 -28
  52. data/benchmarks/mutex_and_monitor.rb +0 -42
  53. data/benchmarks/queue_declare.rb +0 -29
  54. data/benchmarks/queue_declare_and_bind.rb +0 -29
  55. data/benchmarks/queue_declare_bind_and_delete.rb +0 -29
  56. data/benchmarks/synchronized_sorted_set.rb +0 -53
  57. data/benchmarks/write_vs_write_nonblock.rb +0 -49
  58. data/bin/ci/before_build.sh +0 -31
  59. data/bunny.gemspec +0 -40
  60. data/examples/connection/authentication_failure.rb +0 -16
  61. data/examples/connection/automatic_recovery_with_basic_get.rb +0 -40
  62. data/examples/connection/automatic_recovery_with_client_named_queues.rb +0 -36
  63. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +0 -46
  64. data/examples/connection/automatic_recovery_with_server_named_queues.rb +0 -35
  65. data/examples/connection/channel_level_exception.rb +0 -35
  66. data/examples/connection/disabled_automatic_recovery.rb +0 -34
  67. data/examples/connection/heartbeat.rb +0 -17
  68. data/examples/connection/manually_reconnecting_consumer.rb +0 -23
  69. data/examples/connection/manually_reconnecting_publisher.rb +0 -28
  70. data/examples/connection/unknown_host.rb +0 -16
  71. data/examples/guides/exchanges/direct_exchange_routing.rb +0 -36
  72. data/examples/guides/exchanges/fanout_exchange_routing.rb +0 -28
  73. data/examples/guides/exchanges/headers_exchange_routing.rb +0 -31
  74. data/examples/guides/exchanges/mandatory_messages.rb +0 -30
  75. data/examples/guides/extensions/alternate_exchange.rb +0 -28
  76. data/examples/guides/extensions/basic_nack.rb +0 -33
  77. data/examples/guides/extensions/connection_blocked.rb +0 -35
  78. data/examples/guides/extensions/consumer_cancellation_notification.rb +0 -39
  79. data/examples/guides/extensions/dead_letter_exchange.rb +0 -32
  80. data/examples/guides/extensions/exchange_to_exchange_bindings.rb +0 -29
  81. data/examples/guides/extensions/per_message_ttl.rb +0 -36
  82. data/examples/guides/extensions/per_queue_message_ttl.rb +0 -36
  83. data/examples/guides/extensions/publisher_confirms.rb +0 -28
  84. data/examples/guides/extensions/queue_lease.rb +0 -26
  85. data/examples/guides/extensions/sender_selected_distribution.rb +0 -32
  86. data/examples/guides/getting_started/blabbr.rb +0 -27
  87. data/examples/guides/getting_started/hello_world.rb +0 -20
  88. data/examples/guides/getting_started/weathr.rb +0 -47
  89. data/examples/guides/queues/one_off_consumer.rb +0 -23
  90. data/examples/guides/queues/redeliveries.rb +0 -79
  91. data/lib/bunny/compatibility.rb +0 -24
  92. data/lib/bunny/concurrent/linked_continuation_queue.rb +0 -61
  93. data/lib/bunny/jruby/socket.rb +0 -40
  94. data/lib/bunny/jruby/ssl_socket.rb +0 -53
  95. data/lib/bunny/system_timer.rb +0 -20
  96. data/profiling/basic_publish/with_4K_messages.rb +0 -33
  97. data/repl +0 -3
  98. data/spec/compatibility/queue_declare_spec.rb +0 -44
  99. data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
  100. data/spec/higher_level_api/integration/basic_ack_spec.rb +0 -71
  101. data/spec/higher_level_api/integration/basic_cancel_spec.rb +0 -76
  102. data/spec/higher_level_api/integration/basic_consume_spec.rb +0 -225
  103. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +0 -54
  104. data/spec/higher_level_api/integration/basic_get_spec.rb +0 -48
  105. data/spec/higher_level_api/integration/basic_nack_spec.rb +0 -79
  106. data/spec/higher_level_api/integration/basic_publish_spec.rb +0 -89
  107. data/spec/higher_level_api/integration/basic_qos_spec.rb +0 -29
  108. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  109. data/spec/higher_level_api/integration/basic_reject_spec.rb +0 -74
  110. data/spec/higher_level_api/integration/basic_return_spec.rb +0 -33
  111. data/spec/higher_level_api/integration/channel_close_spec.rb +0 -25
  112. data/spec/higher_level_api/integration/channel_flow_spec.rb +0 -21
  113. data/spec/higher_level_api/integration/channel_open_spec.rb +0 -57
  114. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  115. data/spec/higher_level_api/integration/connection_spec.rb +0 -400
  116. data/spec/higher_level_api/integration/connection_stop_spec.rb +0 -26
  117. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  118. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +0 -128
  119. data/spec/higher_level_api/integration/dead_lettering_spec.rb +0 -52
  120. data/spec/higher_level_api/integration/exchange_bind_spec.rb +0 -31
  121. data/spec/higher_level_api/integration/exchange_declare_spec.rb +0 -204
  122. data/spec/higher_level_api/integration/exchange_delete_spec.rb +0 -105
  123. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +0 -40
  124. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +0 -28
  125. data/spec/higher_level_api/integration/heartbeat_spec.rb +0 -31
  126. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  127. data/spec/higher_level_api/integration/message_properties_access_spec.rb +0 -95
  128. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +0 -24
  129. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +0 -77
  130. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +0 -65
  131. data/spec/higher_level_api/integration/queue_bind_spec.rb +0 -109
  132. data/spec/higher_level_api/integration/queue_declare_spec.rb +0 -190
  133. data/spec/higher_level_api/integration/queue_delete_spec.rb +0 -41
  134. data/spec/higher_level_api/integration/queue_purge_spec.rb +0 -30
  135. data/spec/higher_level_api/integration/queue_unbind_spec.rb +0 -54
  136. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +0 -60
  137. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +0 -36
  138. data/spec/higher_level_api/integration/tls_connection_spec.rb +0 -127
  139. data/spec/higher_level_api/integration/tx_commit_spec.rb +0 -21
  140. data/spec/higher_level_api/integration/tx_rollback_spec.rb +0 -21
  141. data/spec/higher_level_api/integration/with_channel_spec.rb +0 -25
  142. data/spec/issues/issue100_spec.rb +0 -42
  143. data/spec/issues/issue141_spec.rb +0 -44
  144. data/spec/issues/issue78_spec.rb +0 -75
  145. data/spec/issues/issue83_spec.rb +0 -31
  146. data/spec/issues/issue97_attachment.json +0 -1
  147. data/spec/issues/issue97_spec.rb +0 -176
  148. data/spec/lower_level_api/integration/basic_cancel_spec.rb +0 -69
  149. data/spec/lower_level_api/integration/basic_consume_spec.rb +0 -100
  150. data/spec/spec_helper.rb +0 -64
  151. data/spec/stress/channel_open_stress_spec.rb +0 -51
  152. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +0 -28
  153. data/spec/stress/concurrent_consumers_stress_spec.rb +0 -69
  154. data/spec/stress/concurrent_publishers_stress_spec.rb +0 -57
  155. data/spec/stress/connection_open_close_spec.rb +0 -40
  156. data/spec/stress/long_running_consumer_spec.rb +0 -83
  157. data/spec/tls/cacert.pem +0 -18
  158. data/spec/tls/client_cert.pem +0 -18
  159. data/spec/tls/client_key.pem +0 -27
  160. data/spec/tls/server_cert.pem +0 -18
  161. data/spec/tls/server_key.pem +0 -27
  162. data/spec/unit/bunny_spec.rb +0 -15
  163. data/spec/unit/concurrent/atomic_fixnum_spec.rb +0 -35
  164. data/spec/unit/concurrent/condition_spec.rb +0 -82
  165. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +0 -35
  166. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +0 -73
  167. data/spec/unit/system_timer_spec.rb +0 -10
  168. data/spec/unit/version_delivery_tag_spec.rb +0 -28
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thread"
2
4
  require "monitor"
3
5
  require "amq/int_allocator"
@@ -17,7 +19,9 @@ module Bunny
17
19
  #
18
20
 
19
21
  # @param [Integer] max_channel Max allowed channel id
20
- def initialize(max_channel = ((1 << 16) - 1))
22
+ def initialize(max_channel = ((1 << 11) - 1))
23
+ # channel 0 has special meaning in the protocol, so start
24
+ # allocator at 1
21
25
  @allocator = AMQ::IntAllocator.new(1, max_channel)
22
26
  @mutex = Monitor.new
23
27
  end
@@ -25,7 +29,7 @@ module Bunny
25
29
 
26
30
  # Returns next available channel id. This method is thread safe.
27
31
  #
28
- # @return [Fixnum]
32
+ # @return [Integer]
29
33
  # @api public
30
34
  # @see ChannelManager#release_channel_id
31
35
  # @see ChannelManager#reset_channel_id_allocator
@@ -37,7 +41,7 @@ module Bunny
37
41
 
38
42
  # Releases previously allocated channel id. This method is thread safe.
39
43
  #
40
- # @param [Fixnum] i Channel id to release
44
+ # @param [Integer] i Channel id to release
41
45
  # @api public
42
46
  # @see ChannelManager#next_channel_id
43
47
  # @see ChannelManager#reset_channel_id_allocator
@@ -51,7 +55,7 @@ module Bunny
51
55
  # Returns true if given channel id has been previously allocated and not yet released.
52
56
  # This method is thread safe.
53
57
  #
54
- # @param [Fixnum] i Channel id to check
58
+ # @param [Integer] i Channel id to check
55
59
  # @return [Boolean] true if given channel id has been previously allocated and not yet released
56
60
  # @api public
57
61
  # @see ChannelManager#next_channel_id
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "set"
2
4
  require "thread"
3
5
  require "monitor"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thread"
2
4
  require "monitor"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thread"
2
4
 
3
5
  module Bunny
@@ -6,36 +8,58 @@ module Bunny
6
8
  #
7
9
  # @private
8
10
  class ContinuationQueue
9
- def initialize(*args, &block)
10
- @q = ::Queue.new(*args)
11
+ def initialize
12
+ @q = []
13
+ @lock = ::Mutex.new
14
+ @cond = ::ConditionVariable.new
11
15
  end
12
16
 
13
- def push(*args)
14
- @q.push(*args)
17
+ def push(item)
18
+ @lock.synchronize do
19
+ @q.push(item)
20
+ @cond.signal
21
+ end
15
22
  end
16
23
  alias << push
17
24
 
18
25
  def pop
19
- @q.pop
26
+ poll
20
27
  end
21
28
 
22
29
  def poll(timeout_in_ms = nil)
23
- if timeout_in_ms
24
- Bunny::Timeout.timeout(timeout_in_ms / 1000, ::Timeout::Error) do
25
- @q.pop
30
+ timeout_in_sec = timeout_in_ms ? timeout_in_ms / 1000.0 : nil
31
+
32
+ @lock.synchronize do
33
+ started_at = Bunny::Timestamp.monotonic
34
+ while @q.empty?
35
+ wait = !(timeout_in_sec.nil?)
36
+ @cond.wait(@lock, timeout_in_sec)
37
+
38
+ if wait
39
+ ended_at = Bunny::Timestamp.monotonic
40
+ elapsed = ended_at - started_at
41
+ raise ::Timeout::Error if (elapsed > timeout_in_sec)
42
+ end
26
43
  end
27
- else
28
- @q.pop
44
+ item = @q.shift
45
+ item
29
46
  end
30
47
  end
31
48
 
32
49
  def clear
33
- @q.clear
50
+ @lock.synchronize do
51
+ @q.clear
52
+ end
53
+ end
54
+
55
+ def empty?
56
+ @q.empty?
34
57
  end
35
58
 
36
- def method_missing(selector, *args, &block)
37
- @q.__send__(selector, *args, &block)
59
+ def size
60
+ @q.size
38
61
  end
62
+ alias length size
39
63
  end
40
64
  end
41
65
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "set"
2
4
  require "thread"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bunny
2
4
  # Base class that represents consumer interface. Subclasses of this class implement
3
5
  # specific logic of handling consumer life cycle events. Note that when the only event
@@ -39,6 +41,8 @@ module Bunny
39
41
  @arguments = arguments
40
42
  # no_ack set to true = no manual ack = automatic ack. MK.
41
43
  @no_ack = no_ack
44
+
45
+ @on_cancellation = []
42
46
  end
43
47
 
44
48
  # Defines message delivery handler
@@ -61,14 +65,16 @@ module Bunny
61
65
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
62
66
  # @api public
63
67
  def on_cancellation(&block)
64
- @on_cancellation = block
68
+ @on_cancellation << block
65
69
  self
66
70
  end
67
71
 
68
72
  # Invokes consumer cancellation notification handler
69
73
  # @private
70
74
  def handle_cancellation(basic_cancel)
71
- @on_cancellation.call(basic_cancel) if @on_cancellation
75
+ @on_cancellation.each do |fn|
76
+ fn.call(basic_cancel)
77
+ end
72
78
  end
73
79
 
74
80
  # Cancels this consumer. Messages for this consumer will no longer be delivered. If the queue
@@ -82,12 +88,12 @@ module Bunny
82
88
 
83
89
  # @return [String] More detailed human-readable string representation of this consumer
84
90
  def inspect
85
- "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name}> @consumer_tag=#{@consumer_tag} @exclusive=#{@exclusive} @no_ack=#{@no_ack}>"
91
+ "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name} @consumer_tag=#{@consumer_tag} @exclusive=#{@exclusive} @no_ack=#{@no_ack}>"
86
92
  end
87
93
 
88
94
  # @return [String] Brief human-readable string representation of this consumer
89
95
  def to_s
90
- "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name}> @consumer_tag=#{@consumer_tag}>"
96
+ "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name} @consumer_tag=#{@consumer_tag}>"
91
97
  end
92
98
 
93
99
  # @return [Boolean] true if this consumer uses automatic acknowledgement mode
@@ -102,6 +108,16 @@ module Bunny
102
108
  @no_ack == false
103
109
  end
104
110
 
111
+ # @return [String] Name of the queue this consumer is on
112
+ # @api public
113
+ def queue_name
114
+ if @queue.respond_to?(:name)
115
+ @queue.name
116
+ else
117
+ @queue
118
+ end
119
+ end
120
+
105
121
  #
106
122
  # Recovery
107
123
  #
@@ -110,14 +126,5 @@ module Bunny
110
126
  def recover_from_network_failure
111
127
  @channel.basic_consume_with(self)
112
128
  end
113
-
114
- # @private
115
- def queue_name
116
- if @queue.respond_to?(:name)
117
- @queue.name
118
- else
119
- @queue
120
- end
121
- end
122
129
  end
123
130
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bunny
2
4
  # Used to generate consumer tags in the client
3
5
  class ConsumerTagGenerator
@@ -8,7 +10,8 @@ module Bunny
8
10
 
9
11
  # @return [String] Generated consumer tag
10
12
  def generate
11
- "#{Kernel.rand}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
13
+ t = Bunny::Timestamp.now
14
+ "#{Kernel.rand}-#{t.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
12
15
  end # generate
13
16
 
14
17
 
@@ -17,7 +20,8 @@ module Bunny
17
20
  # @return [String] Unique string.
18
21
  # @api public
19
22
  def generate_prefixed(name = "bunny")
20
- "#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
23
+ t = Bunny::Timestamp.now
24
+ "#{name}-#{t.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
21
25
  end
22
26
  end
23
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thread"
2
4
 
3
5
  module Bunny
@@ -15,10 +17,17 @@ module Bunny
15
17
 
16
18
  attr_reader :threads
17
19
  attr_reader :size
20
+ attr_reader :abort_on_exception
18
21
 
19
- def initialize(size = 1)
22
+ def initialize(size = 1, abort_on_exception = false, shutdown_timeout = 60)
20
23
  @size = size
24
+ @abort_on_exception = abort_on_exception
25
+ @shutdown_timeout = shutdown_timeout
26
+ @shutdown_mutex = ::Mutex.new
27
+ @shutdown_conditional = ::ConditionVariable.new
21
28
  @queue = ::Queue.new
29
+ @paused = false
30
+ @running = false
22
31
  end
23
32
 
24
33
 
@@ -31,6 +40,7 @@ module Bunny
31
40
 
32
41
  @size.times do
33
42
  t = Thread.new(&method(:run_loop))
43
+ t.abort_on_exception = true if abort_on_exception
34
44
  @threads << t
35
45
  end
36
46
 
@@ -41,7 +51,16 @@ module Bunny
41
51
  @running
42
52
  end
43
53
 
44
- def shutdown
54
+ def backlog
55
+ @queue.length
56
+ end
57
+
58
+ def busy?
59
+ !@queue.empty?
60
+ end
61
+
62
+ def shutdown(wait_for_workers = false)
63
+ was_running = running?
45
64
  @running = false
46
65
 
47
66
  @size.times do
@@ -49,20 +68,26 @@ module Bunny
49
68
  throw :terminate
50
69
  end
51
70
  end
71
+
72
+ return if !(wait_for_workers && @shutdown_timeout && was_running)
73
+
74
+ @shutdown_mutex.synchronize do
75
+ @shutdown_conditional.wait(@shutdown_mutex, @shutdown_timeout) if busy?
76
+ end
52
77
  end
53
78
 
54
79
  def join(timeout = nil)
55
- @threads.each { |t| t.join(timeout) }
80
+ (@threads || []).each { |t| t.join(timeout) }
56
81
  end
57
82
 
58
83
  def pause
59
84
  @running = false
60
-
61
- @threads.each { |t| t.stop }
85
+ @paused = true
62
86
  end
63
87
 
64
88
  def resume
65
89
  @running = true
90
+ @paused = false
66
91
 
67
92
  @threads.each { |t| t.run }
68
93
  end
@@ -70,7 +95,7 @@ module Bunny
70
95
  def kill
71
96
  @running = false
72
97
 
73
- @threads.each { |t| t.kill }
98
+ (@threads || []).each { |t| t.kill }
74
99
  end
75
100
 
76
101
  protected
@@ -78,17 +103,22 @@ module Bunny
78
103
  def run_loop
79
104
  catch(:terminate) do
80
105
  loop do
106
+ Thread.stop if @paused
81
107
  callable = @queue.pop
82
108
 
83
109
  begin
84
110
  callable.call
85
- rescue ::Exception => e
111
+ rescue ::StandardError => e
86
112
  # TODO: use connection logger
87
113
  $stderr.puts e.class.name
88
114
  $stderr.puts e.message
89
115
  end
90
116
  end
91
117
  end
118
+
119
+ @shutdown_mutex.synchronize do
120
+ @shutdown_conditional.signal unless busy?
121
+ end
92
122
  end
93
123
  end
94
124
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "socket"
2
4
 
3
5
  module Bunny
@@ -6,23 +8,40 @@ module Bunny
6
8
  #
7
9
  # Heavily inspired by Dalli by Mike Perham.
8
10
  # @private
9
- class Socket < TCPSocket
11
+ module Socket
10
12
  attr_accessor :options
11
13
 
12
- # IO::WaitReadable is 1.9+ only
13
- READ_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
14
- READ_RETRY_EXCEPTION_CLASSES << IO::WaitReadable if IO.const_defined?(:WaitReadable)
14
+ READ_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitReadable)
15
+ # Ruby 2.1+
16
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable,
17
+ IO::EAGAINWaitReadable, IO::EWOULDBLOCKWaitReadable]
18
+ else
19
+ # 2.0
20
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable]
21
+ end
22
+ WRITE_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitWritable)
23
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable,
24
+ IO::EAGAINWaitWritable, IO::EWOULDBLOCKWaitWritable]
25
+ else
26
+ # 2.0
27
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
28
+ end
15
29
 
16
30
  def self.open(host, port, options = {})
17
- Timeout.timeout(options[:socket_timeout], ClientTimeout) do
18
- sock = new(host, port)
19
- if Socket.constants.include?('TCP_NODELAY') || Socket.constants.include?(:TCP_NODELAY)
20
- sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
21
- end
22
- sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
23
- sock.options = {:host => host, :port => port}.merge(options)
24
- sock
31
+ socket = ::Socket.tcp(host, port, nil, nil,
32
+ connect_timeout: options[:connect_timeout])
33
+ if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY)
34
+ socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
25
35
  end
36
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
37
+ socket.instance_eval do
38
+ @__bunny_socket_eof_flag__ = false
39
+ end
40
+ socket.extend self
41
+ socket.options = { :host => host, :port => port }.merge(options)
42
+ socket
43
+ rescue Errno::ETIMEDOUT
44
+ raise ClientTimeout
26
45
  end
27
46
 
28
47
  # Reads given number of bytes with an optional timeout
@@ -35,7 +54,7 @@ module Bunny
35
54
  def read_fully(count, timeout = nil)
36
55
  return nil if @__bunny_socket_eof_flag__
37
56
 
38
- value = ''
57
+ value = +''
39
58
  begin
40
59
  loop do
41
60
  value << read_nonblock(count - value.bytesize)
@@ -62,22 +81,32 @@ module Bunny
62
81
  # if this is not appropriate in your case.
63
82
  #
64
83
  # @param [String] data Data to write
84
+ # @param [Integer] timeout Timeout
65
85
  #
66
86
  # @api public
67
- def write_nonblock_fully(data)
87
+ def write_nonblock_fully(data, timeout = nil)
68
88
  return nil if @__bunny_socket_eof_flag__
69
89
 
70
- begin
71
- while !data.empty?
72
- written = self.write_nonblock(data)
73
- data.slice!(0, written)
90
+ length = data.bytesize
91
+ total_count = 0
92
+ count = 0
93
+ loop do
94
+ begin
95
+ count = self.write_nonblock(data)
96
+ rescue *WRITE_RETRY_EXCEPTION_CLASSES
97
+ if IO.select([], [self], nil, timeout)
98
+ retry
99
+ else
100
+ raise Timeout::Error, "IO timeout when writing to socket"
101
+ end
74
102
  end
75
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN
76
- IO.select([], [self])
77
- retry
103
+
104
+ total_count += count
105
+ return total_count if total_count >= length
106
+ data = data.byteslice(count..-1)
78
107
  end
79
108
 
80
- data.bytesize
81
109
  end
110
+
82
111
  end
83
112
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "socket"
2
4
 
3
5
  module Bunny
@@ -8,10 +10,26 @@ module Bunny
8
10
  # methods found in Bunny::Socket.
9
11
  class SSLSocket < OpenSSL::SSL::SSLSocket
10
12
 
11
- # IO::WaitReadable is 1.9+ only
12
- READ_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
13
- READ_RETRY_EXCEPTION_CLASSES << IO::WaitReadable if IO.const_defined?(:WaitReadable)
13
+ READ_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitReadable)
14
+ # Ruby 2.1+
15
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable,
16
+ IO::EAGAINWaitReadable, IO::EWOULDBLOCKWaitReadable]
17
+ else
18
+ # 2.0
19
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable]
20
+ end
21
+ WRITE_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitWritable)
22
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable,
23
+ IO::EAGAINWaitWritable, IO::EWOULDBLOCKWaitWritable]
24
+ else
25
+ # 2.0
26
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
27
+ end
14
28
 
29
+ def initialize(*args)
30
+ super
31
+ @__bunny_socket_eof_flag__ = false
32
+ end
15
33
 
16
34
  # Reads given number of bytes with an optional timeout
17
35
  #
@@ -23,7 +41,7 @@ module Bunny
23
41
  def read_fully(count, timeout = nil)
24
42
  return nil if @__bunny_socket_eof_flag__
25
43
 
26
- value = ''
44
+ value = +''
27
45
  begin
28
46
  loop do
29
47
  value << read_nonblock(count - value.bytesize)
@@ -50,8 +68,53 @@ module Bunny
50
68
  end
51
69
  value
52
70
  end
71
+
72
+ # Writes provided data using IO#write_nonblock, taking care of handling
73
+ # of exceptions it raises when writing would fail (e.g. due to socket buffer
74
+ # being full).
75
+ #
76
+ # IMPORTANT: this method will mutate (slice) the argument. Pass in duplicates
77
+ # if this is not appropriate in your case.
78
+ #
79
+ # @param [String] data Data to write
80
+ # @param [Integer] timeout Timeout
81
+ #
82
+ # @api public
83
+ def write_nonblock_fully(data, timeout = nil)
84
+ return nil if @__bunny_socket_eof_flag__
85
+
86
+ length = data.bytesize
87
+ total_count = 0
88
+ count = 0
89
+ loop do
90
+ begin
91
+ count = self.write_nonblock(data)
92
+ rescue OpenSSL::SSL::SSLError => e
93
+ if e.message == "write would block"
94
+ if IO.select([], [self], nil, timeout)
95
+ retry
96
+ else
97
+ raise Timeout::Error, "IO timeout when writing to socket"
98
+ end
99
+ end
100
+ raise e
101
+ rescue *WRITE_RETRY_EXCEPTION_CLASSES
102
+ if IO.select([], [self], nil, timeout)
103
+ retry
104
+ else
105
+ raise Timeout::Error, "IO timeout when writing to socket"
106
+ end
107
+ end
108
+
109
+ total_count += count
110
+ return total_count if total_count >= length
111
+ data = data.byteslice(count..-1)
112
+ end
113
+
114
+ end
115
+
53
116
  end
54
- rescue LoadError => le
117
+ rescue LoadError
55
118
  puts "Could not load OpenSSL"
56
119
  end
57
120
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bunny/versioned_delivery_tag"
2
4
 
3
5
  module Bunny
@@ -37,7 +39,7 @@ module Bunny
37
39
  @channel = channel
38
40
  end
39
41
 
40
- # Iterates over the delivery properties
42
+ # Iterates over delivery properties
41
43
  # @see Enumerable#each
42
44
  def each(*args, &block)
43
45
  @hash.each(*args, &block)
@@ -1,9 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bunny
2
4
  # Base class for all Bunny exceptions
3
5
  # @api public
4
6
  class Exception < ::StandardError
5
7
  end
6
8
 
9
+ class HostListDepleted < Exception
10
+ def initialize
11
+ super("No more hosts to try in the supplied list of hosts")
12
+ end
13
+ end
14
+
7
15
  # Indicates a network failure. If automatic network
8
16
  # recovery mode is enabled, these will be typically handled
9
17
  # by the client itself.
@@ -54,20 +62,29 @@ module Bunny
54
62
  end
55
63
  end
56
64
 
57
-
58
65
  # Raised when TCP connection to RabbitMQ fails because of an unresolved
59
66
  # hostname, connectivity problem, etc
60
67
  class TCPConnectionFailed < Exception
61
68
  attr_reader :hostname, :port
62
69
 
63
- def initialize(e, hostname, port)
70
+ def initialize(e, hostname=nil, port=nil)
64
71
  m = case e
65
72
  when String then
66
73
  e
67
- when Exception then
74
+ when ::Exception then
68
75
  e.message
69
76
  end
70
- super("Could not establish TCP connection to #{hostname}:#{port}: #{m}")
77
+ if hostname && port
78
+ super("Could not establish TCP connection to #{hostname}:#{port}: #{m}")
79
+ else
80
+ super(m)
81
+ end
82
+ end
83
+ end
84
+
85
+ class TCPConnectionFailedForAllHosts < TCPConnectionFailed
86
+ def initialize
87
+ super("Could not establish TCP connection to any of the configured hosts", nil, nil)
71
88
  end
72
89
  end
73
90
 
@@ -82,6 +99,12 @@ module Bunny
82
99
  end
83
100
  end
84
101
 
102
+ class ConnectionAlreadyClosed < Exception
103
+ def initialize
104
+ super('Connection has been already closed')
105
+ end
106
+ end
107
+
85
108
  class ShutdownSignal < Exception
86
109
  end
87
110