bunny 0.9.0.pre7 → 0.9.0.pre8
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.
- data/ChangeLog.md +22 -0
- data/Gemfile +2 -2
- data/README.md +2 -2
- data/examples/connection/disabled_automatic_recovery.rb +34 -0
- data/lib/bunny.rb +8 -0
- data/lib/bunny/channel.rb +16 -13
- data/lib/bunny/channel_id_allocator.rb +16 -13
- data/lib/bunny/exceptions.rb +45 -33
- data/lib/bunny/main_loop.rb +27 -12
- data/lib/bunny/queue.rb +1 -1
- data/lib/bunny/session.rb +54 -10
- data/lib/bunny/socket.rb +2 -2
- data/lib/bunny/ssl_socket.rb +33 -0
- data/lib/bunny/transport.rb +124 -40
- data/lib/bunny/version.rb +1 -1
- data/spec/higher_level_api/integration/basic_consume_spec.rb +39 -0
- data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +51 -0
- data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +3 -3
- data/spec/issues/issue100_spec.rb +40 -0
- data/spec/issues/issue97_spec.rb +13 -11
- data/spec/stress/channel_open_stress_spec.rb +49 -0
- data/spec/stress/concurrent_consumers_stress_spec.rb +66 -0
- data/spec/stress/concurrent_publishers_stress_spec.rb +58 -0
- data/spec/unit/concurrent/condition_spec.rb +5 -0
- metadata +14 -4
- data/spec/higher_level_api/integration/channel_open_stress_spec.rb +0 -22
data/ChangeLog.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
## Changes between Bunny 0.9.0.pre7 and 0.9.0.pre8
|
2
|
+
|
3
|
+
### Automatic Connection Recovery Can Be Disabled
|
4
|
+
|
5
|
+
Automatic connection recovery now can be disabled by passing
|
6
|
+
the `:automatically_recover => false` option to `Bunny#initialize`).
|
7
|
+
|
8
|
+
When the recovery is disabled, network I/O-related exceptions will
|
9
|
+
cause an exception to be raised in thee thread the connection was
|
10
|
+
started on.
|
11
|
+
|
12
|
+
|
13
|
+
### No Timeout Control For Publishing
|
14
|
+
|
15
|
+
`Bunny::Exchange#publish` and `Bunny::Channel#basic_publish` no
|
16
|
+
longer perform timeout control (using the timeout module) which
|
17
|
+
roughly increases throughput for flood publishing by 350%.
|
18
|
+
|
19
|
+
Apps that need delivery guarantees should use publisher confirms.
|
20
|
+
|
21
|
+
|
22
|
+
|
1
23
|
## Changes between Bunny 0.9.0.pre6 and 0.9.0.pre7
|
2
24
|
|
3
25
|
### Bunny::Channel#on_error
|
data/Gemfile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
source
|
3
|
+
source "https://rubygems.org"
|
4
4
|
|
5
5
|
# Use local clones if possible.
|
6
6
|
# If you want to use your local copy, just symlink it to vendor.
|
@@ -26,7 +26,7 @@ gem "effin_utf8"
|
|
26
26
|
|
27
27
|
group :development do
|
28
28
|
gem "yard"
|
29
|
-
gem "redcarpet"
|
29
|
+
gem "redcarpet", :platform => :mri
|
30
30
|
end
|
31
31
|
|
32
32
|
group :test do
|
data/README.md
CHANGED
@@ -36,7 +36,7 @@ gem install bunny --pre
|
|
36
36
|
To use Bunny 0.9.x in a project managed with Bundler:
|
37
37
|
|
38
38
|
``` ruby
|
39
|
-
gem "bunny", ">= 0.9.0.
|
39
|
+
gem "bunny", ">= 0.9.0.pre7" # optionally: , :git => "git://github.com/ruby-amqp/bunny.git", :branch => "master"
|
40
40
|
```
|
41
41
|
|
42
42
|
|
@@ -95,7 +95,7 @@ Other documentation guides are available at [rubybunny.info](http://rubybunny.in
|
|
95
95
|
|
96
96
|
### Mailing List
|
97
97
|
|
98
|
-
[Bunny a mailing list](groups.google.com/group/ruby-amqp). We encourage you
|
98
|
+
[Bunny a mailing list](http://groups.google.com/group/ruby-amqp). We encourage you
|
99
99
|
to also join the [rabbitmq-discuss](https://lists.rabbitmq.com/cgi-bin/mailman/listinfo/rabbitmq-discuss) mailing list. Feel free to ask any questions that you may have.
|
100
100
|
|
101
101
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require "bundler"
|
5
|
+
Bundler.setup
|
6
|
+
|
7
|
+
$:.unshift(File.expand_path("../../../lib", __FILE__))
|
8
|
+
|
9
|
+
require 'bunny'
|
10
|
+
|
11
|
+
conn = Bunny.new(:heartbeat_interval => 8, :automatically_recover => false)
|
12
|
+
conn.start
|
13
|
+
|
14
|
+
ch = conn.create_channel
|
15
|
+
x = ch.topic("bunny.examples.recovery.topic", :durable => false)
|
16
|
+
q = ch.queue("bunny.examples.recovery.client_named_queue2", :durable => true)
|
17
|
+
q.purge
|
18
|
+
|
19
|
+
q.bind(x, :routing_key => "abc").bind(x, :routing_key => "def")
|
20
|
+
|
21
|
+
loop do
|
22
|
+
sleep 1.5
|
23
|
+
body = rand.to_s
|
24
|
+
puts "Published #{body}"
|
25
|
+
x.publish(body, :routing_key => ["abc", "def"].sample)
|
26
|
+
|
27
|
+
sleep 1.5
|
28
|
+
_, _, payload = q.pop
|
29
|
+
if payload
|
30
|
+
puts "Consumed #{payload}"
|
31
|
+
else
|
32
|
+
puts "Consumed nothing"
|
33
|
+
end
|
34
|
+
end
|
data/lib/bunny.rb
CHANGED
@@ -9,6 +9,14 @@ require "bunny/framing"
|
|
9
9
|
require "bunny/exceptions"
|
10
10
|
require "bunny/socket"
|
11
11
|
|
12
|
+
begin
|
13
|
+
require "openssl"
|
14
|
+
|
15
|
+
require "bunny/ssl_socket"
|
16
|
+
rescue LoadError => e
|
17
|
+
# no-op
|
18
|
+
end
|
19
|
+
|
12
20
|
# Core entities: connection, channel, exchange, queue, consumer
|
13
21
|
require "bunny/session"
|
14
22
|
require "bunny/channel"
|
data/lib/bunny/channel.rb
CHANGED
@@ -342,6 +342,7 @@ module Bunny
|
|
342
342
|
# @param [String] name Exchange name
|
343
343
|
# @param [Hash] opts Exchange parameters
|
344
344
|
#
|
345
|
+
# @option opts [String,Symbol] :type (:direct) Exchange type, e.g. :fanout or "x-consistent-hash"
|
345
346
|
# @option opts [Boolean] :durable (false) Should the exchange be durable?
|
346
347
|
# @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use?
|
347
348
|
# @option opts [Hash] :arguments ({}) Optional exchange arguments
|
@@ -509,14 +510,15 @@ module Bunny
|
|
509
510
|
@next_publish_seq_no += 1
|
510
511
|
end
|
511
512
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
513
|
+
m = AMQ::Protocol::Basic::Publish.encode(@id,
|
514
|
+
payload,
|
515
|
+
meta,
|
516
|
+
exchange_name,
|
517
|
+
routing_key,
|
518
|
+
meta[:mandatory],
|
519
|
+
false,
|
520
|
+
@connection.frame_max)
|
521
|
+
@connection.send_frameset_without_timeout(m, self)
|
520
522
|
|
521
523
|
self
|
522
524
|
end
|
@@ -782,6 +784,12 @@ module Bunny
|
|
782
784
|
queue
|
783
785
|
end
|
784
786
|
|
787
|
+
# helps avoid race condition between basic.consume-ok and basic.deliver if there are messages
|
788
|
+
# in the queue already. MK.
|
789
|
+
if consumer_tag && consumer_tag.strip != AMQ::Protocol::EMPTY_STRING
|
790
|
+
add_consumer(queue_name, consumer_tag, no_ack, exclusive, arguments, &block)
|
791
|
+
end
|
792
|
+
|
785
793
|
@connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id,
|
786
794
|
queue_name,
|
787
795
|
consumer_tag,
|
@@ -790,11 +798,6 @@ module Bunny
|
|
790
798
|
exclusive,
|
791
799
|
false,
|
792
800
|
arguments))
|
793
|
-
# helps avoid race condition between basic.consume-ok and basic.deliver if there are messages
|
794
|
-
# in the queue already. MK.
|
795
|
-
if consumer_tag && consumer_tag.strip != AMQ::Protocol::EMPTY_STRING
|
796
|
-
add_consumer(queue_name, consumer_tag, no_ack, exclusive, arguments, &block)
|
797
|
-
end
|
798
801
|
|
799
802
|
Bunny::Timer.timeout(read_write_timeout, ClientTimeout) do
|
800
803
|
@last_basic_consume_ok = wait_on_continuations
|
@@ -10,9 +10,8 @@ module Bunny
|
|
10
10
|
|
11
11
|
# @param [Integer] max_channel Max allowed channel id
|
12
12
|
def initialize(max_channel = ((1 << 16) - 1))
|
13
|
-
@
|
14
|
-
|
15
|
-
@channel_id_mutex ||= Mutex.new
|
13
|
+
@allocator = AMQ::IntAllocator.new(1, max_channel)
|
14
|
+
@mutex = Mutex.new
|
16
15
|
end
|
17
16
|
|
18
17
|
|
@@ -23,8 +22,8 @@ module Bunny
|
|
23
22
|
# @see ChannelManager#release_channel_id
|
24
23
|
# @see ChannelManager#reset_channel_id_allocator
|
25
24
|
def next_channel_id
|
26
|
-
@
|
27
|
-
@
|
25
|
+
@mutex.synchronize do
|
26
|
+
@allocator.allocate
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
@@ -35,10 +34,10 @@ module Bunny
|
|
35
34
|
# @see ChannelManager#next_channel_id
|
36
35
|
# @see ChannelManager#reset_channel_id_allocator
|
37
36
|
def release_channel_id(i)
|
38
|
-
@
|
39
|
-
@
|
37
|
+
@mutex.synchronize do
|
38
|
+
@allocator.release(i)
|
40
39
|
end
|
41
|
-
end
|
40
|
+
end
|
42
41
|
|
43
42
|
|
44
43
|
# Returns true if given channel id has been previously allocated and not yet released.
|
@@ -50,8 +49,8 @@ module Bunny
|
|
50
49
|
# @see ChannelManager#next_channel_id
|
51
50
|
# @see ChannelManager#release_channel_id
|
52
51
|
def allocated_channel_id?(i)
|
53
|
-
@
|
54
|
-
@
|
52
|
+
@mutex.synchronize do
|
53
|
+
@allocator.allocated?(i)
|
55
54
|
end
|
56
55
|
end
|
57
56
|
|
@@ -60,9 +59,13 @@ module Bunny
|
|
60
59
|
# @see Channel.next_channel_id
|
61
60
|
# @see Channel.release_channel_id
|
62
61
|
def reset_channel_id_allocator
|
63
|
-
@
|
64
|
-
@
|
62
|
+
@mutex.synchronize do
|
63
|
+
@allocator.reset
|
65
64
|
end
|
66
|
-
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def synchronize(&block)
|
68
|
+
@mutex.synchronize(&block)
|
69
|
+
end
|
67
70
|
end
|
68
71
|
end
|
data/lib/bunny/exceptions.rb
CHANGED
@@ -1,5 +1,40 @@
|
|
1
1
|
module Bunny
|
2
|
-
class
|
2
|
+
class Exception < ::Exception
|
3
|
+
end
|
4
|
+
|
5
|
+
class NetworkFailure < Exception
|
6
|
+
attr_reader :cause
|
7
|
+
|
8
|
+
def initialize(message, cause)
|
9
|
+
super(message)
|
10
|
+
@cause = cause
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ChannelLevelException < Exception
|
15
|
+
attr_reader :channel, :channel_close
|
16
|
+
|
17
|
+
def initialize(message, ch, channel_close)
|
18
|
+
super(message)
|
19
|
+
|
20
|
+
@channel = ch
|
21
|
+
@channel_close = channel_close
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ConnectionLevelException < Exception
|
26
|
+
attr_reader :connection, :connection_close
|
27
|
+
|
28
|
+
def initialize(message, connection, connection_close)
|
29
|
+
super(message)
|
30
|
+
|
31
|
+
@connection = connection
|
32
|
+
@connection_close = connection_close
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class TCPConnectionFailed < Exception
|
3
38
|
attr_reader :hostname, :port
|
4
39
|
|
5
40
|
def initialize(e, hostname, port)
|
@@ -13,7 +48,7 @@ module Bunny
|
|
13
48
|
end
|
14
49
|
end
|
15
50
|
|
16
|
-
class ConnectionClosedError <
|
51
|
+
class ConnectionClosedError < Exception
|
17
52
|
def initialize(frame)
|
18
53
|
if frame.respond_to?(:method_class)
|
19
54
|
super("Trying to send frame through a closed connection. Frame is #{frame.inspect}, method class is #{frame.method_class}")
|
@@ -23,7 +58,7 @@ module Bunny
|
|
23
58
|
end
|
24
59
|
end
|
25
60
|
|
26
|
-
class PossibleAuthenticationFailureError <
|
61
|
+
class PossibleAuthenticationFailureError < Exception
|
27
62
|
|
28
63
|
#
|
29
64
|
# API
|
@@ -44,10 +79,10 @@ module Bunny
|
|
44
79
|
ConnectionError = TCPConnectionFailed
|
45
80
|
ServerDownError = TCPConnectionFailed
|
46
81
|
|
47
|
-
class ForcedChannelCloseError <
|
48
|
-
class ForcedConnectionCloseError <
|
49
|
-
class MessageError <
|
50
|
-
class ProtocolError <
|
82
|
+
class ForcedChannelCloseError < Exception; end
|
83
|
+
class ForcedConnectionCloseError < Exception; end
|
84
|
+
class MessageError < Exception; end
|
85
|
+
class ProtocolError < Exception; end
|
51
86
|
|
52
87
|
# raised when read or write I/O operations time out (but only if
|
53
88
|
# a connection is configured to use them)
|
@@ -57,7 +92,7 @@ module Bunny
|
|
57
92
|
|
58
93
|
|
59
94
|
# Base exception class for data consistency and framing errors.
|
60
|
-
class InconsistentDataError <
|
95
|
+
class InconsistentDataError < Exception
|
61
96
|
end
|
62
97
|
|
63
98
|
# Raised by adapters when frame does not end with {final octet AMQ::Protocol::Frame::FINAL_OCTET}.
|
@@ -82,7 +117,7 @@ module Bunny
|
|
82
117
|
end
|
83
118
|
|
84
119
|
|
85
|
-
class ChannelAlreadyClosed <
|
120
|
+
class ChannelAlreadyClosed < Exception
|
86
121
|
attr_reader :channel
|
87
122
|
|
88
123
|
def initialize(message, ch)
|
@@ -92,17 +127,6 @@ module Bunny
|
|
92
127
|
end
|
93
128
|
end
|
94
129
|
|
95
|
-
class ChannelLevelException < StandardError
|
96
|
-
attr_reader :channel, :channel_close
|
97
|
-
|
98
|
-
def initialize(message, ch, channel_close)
|
99
|
-
super(message)
|
100
|
-
|
101
|
-
@channel = ch
|
102
|
-
@channel_close = channel_close
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
130
|
class PreconditionFailed < ChannelLevelException
|
107
131
|
end
|
108
132
|
|
@@ -116,18 +140,6 @@ module Bunny
|
|
116
140
|
end
|
117
141
|
|
118
142
|
|
119
|
-
|
120
|
-
class ConnectionLevelException < StandardError
|
121
|
-
attr_reader :connection, :connection_close
|
122
|
-
|
123
|
-
def initialize(message, connection, connection_close)
|
124
|
-
super(message)
|
125
|
-
|
126
|
-
@connection = connection
|
127
|
-
@connection_close = connection_close
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
143
|
class ChannelError < ConnectionLevelException
|
132
144
|
end
|
133
145
|
|
@@ -137,7 +149,7 @@ module Bunny
|
|
137
149
|
class UnexpectedFrame < ConnectionLevelException
|
138
150
|
end
|
139
151
|
|
140
|
-
class NetworkErrorWrapper <
|
152
|
+
class NetworkErrorWrapper < Exception
|
141
153
|
attr_reader :other
|
142
154
|
|
143
155
|
def initialize(other)
|
data/lib/bunny/main_loop.rb
CHANGED
@@ -8,9 +8,10 @@ module Bunny
|
|
8
8
|
# This mimics the way RabbitMQ Java is designed quite closely.
|
9
9
|
class MainLoop
|
10
10
|
|
11
|
-
def initialize(transport, session)
|
12
|
-
@transport
|
13
|
-
@session
|
11
|
+
def initialize(transport, session, session_thread)
|
12
|
+
@transport = transport
|
13
|
+
@session = session
|
14
|
+
@session_thread = session_thread
|
14
15
|
end
|
15
16
|
|
16
17
|
|
@@ -29,19 +30,25 @@ module Bunny
|
|
29
30
|
begin
|
30
31
|
break if @stopping || @network_is_down
|
31
32
|
run_once
|
32
|
-
rescue Timeout::Error => te
|
33
|
-
# given that the server may be pushing data to us, timeout detection/handling
|
34
|
-
# should happen per operation and not in this loop
|
35
33
|
rescue Errno::EBADF => ebadf
|
36
34
|
# ignored, happens when we loop after the transport has already been closed
|
37
|
-
rescue AMQ::Protocol::EmptyResponseError, IOError,
|
38
|
-
puts "Exception in the main loop:
|
35
|
+
rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError => e
|
36
|
+
puts "Exception in the main loop:"
|
37
|
+
log_exception(e)
|
38
|
+
|
39
39
|
@network_is_down = true
|
40
|
-
|
40
|
+
|
41
|
+
if @session.automatically_recover?
|
42
|
+
@session.handle_network_failure(e)
|
43
|
+
else
|
44
|
+
@session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
45
|
+
end
|
41
46
|
rescue Exception => e
|
42
|
-
puts
|
43
|
-
|
44
|
-
|
47
|
+
puts "Unepxected exception in the main loop:"
|
48
|
+
log_exception(e)
|
49
|
+
|
50
|
+
@network_is_down = true
|
51
|
+
@session_thread.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
|
45
52
|
end
|
46
53
|
end
|
47
54
|
end
|
@@ -79,5 +86,13 @@ module Bunny
|
|
79
86
|
@thread.kill
|
80
87
|
@thread.join
|
81
88
|
end
|
89
|
+
|
90
|
+
def log_exception(e)
|
91
|
+
puts e.class.name
|
92
|
+
puts e.message
|
93
|
+
e.backtrace.each do |line|
|
94
|
+
puts line
|
95
|
+
end
|
96
|
+
end
|
82
97
|
end
|
83
98
|
end
|
data/lib/bunny/queue.rb
CHANGED
@@ -208,7 +208,7 @@ module Bunny
|
|
208
208
|
|
209
209
|
# @param [Hash] opts Options
|
210
210
|
#
|
211
|
-
# @option opts [Boolean]
|
211
|
+
# @option opts [Boolean] :ack (false) Will the message be acknowledged manually?
|
212
212
|
#
|
213
213
|
# @return [Array] Triple of delivery info, message properties and message content.
|
214
214
|
# If the queue is empty, all three will be nils.
|
data/lib/bunny/session.rb
CHANGED
@@ -101,6 +101,13 @@ module Bunny
|
|
101
101
|
@logging = opts[:logging] || false
|
102
102
|
@threaded = opts.fetch(:threaded, true)
|
103
103
|
|
104
|
+
# should automatic recovery from network failures be used?
|
105
|
+
@automatically_recover = if opts[:automatically_recover].nil? && opts[:automatic_recovery].nil?
|
106
|
+
true
|
107
|
+
else
|
108
|
+
opts[:automatically_recover] || opts[:automatic_recovery]
|
109
|
+
end
|
110
|
+
|
104
111
|
@status = :not_connected
|
105
112
|
|
106
113
|
# these are negotiated with the broker during the connection tuning phase
|
@@ -112,7 +119,9 @@ module Bunny
|
|
112
119
|
@mechanism = opts.fetch(:auth_mechanism, "PLAIN")
|
113
120
|
@credentials_encoder = credentials_encoder_for(@mechanism)
|
114
121
|
@locale = @opts.fetch(:locale, DEFAULT_LOCALE)
|
122
|
+
# mutex for the channel id => channel hash
|
115
123
|
@channel_mutex = Mutex.new
|
124
|
+
@network_mutex = Mutex.new
|
116
125
|
@channels = Hash.new
|
117
126
|
|
118
127
|
@continuations = ::Queue.new
|
@@ -210,6 +219,10 @@ module Bunny
|
|
210
219
|
end
|
211
220
|
alias connected? open?
|
212
221
|
|
222
|
+
def automatically_recover?
|
223
|
+
@automatically_recover
|
224
|
+
end
|
225
|
+
|
213
226
|
#
|
214
227
|
# Backwards compatibility
|
215
228
|
#
|
@@ -254,7 +267,9 @@ module Bunny
|
|
254
267
|
n = ch.number
|
255
268
|
self.register_channel(ch)
|
256
269
|
|
257
|
-
@
|
270
|
+
@channel_mutex.synchronize do
|
271
|
+
@transport.send_frame(AMQ::Protocol::Channel::Open.encode(n, AMQ::Protocol::EMPTY_STRING))
|
272
|
+
end
|
258
273
|
@last_channel_open_ok = wait_on_continuations
|
259
274
|
raise_if_continuation_resulted_in_a_connection_error!
|
260
275
|
|
@@ -317,8 +332,7 @@ module Bunny
|
|
317
332
|
puts e.message
|
318
333
|
puts e.backtrace
|
319
334
|
ensure
|
320
|
-
@
|
321
|
-
@active_continuation = false
|
335
|
+
@continuations.push(nil)
|
322
336
|
end
|
323
337
|
when AMQ::Protocol::Channel::Close then
|
324
338
|
begin
|
@@ -420,8 +434,8 @@ module Bunny
|
|
420
434
|
end
|
421
435
|
|
422
436
|
# @private
|
423
|
-
def send_raw(
|
424
|
-
@transport.write(
|
437
|
+
def send_raw(data)
|
438
|
+
@transport.write(data)
|
425
439
|
end
|
426
440
|
|
427
441
|
# @private
|
@@ -513,7 +527,7 @@ module Bunny
|
|
513
527
|
|
514
528
|
# @private
|
515
529
|
def event_loop
|
516
|
-
@event_loop ||= MainLoop.new(@transport, self)
|
530
|
+
@event_loop ||= MainLoop.new(@transport, self, Thread.current)
|
517
531
|
end
|
518
532
|
|
519
533
|
# @private
|
@@ -531,7 +545,21 @@ module Bunny
|
|
531
545
|
if closed?
|
532
546
|
raise ConnectionClosedError.new(frame)
|
533
547
|
else
|
534
|
-
@transport.
|
548
|
+
@network_mutex.synchronize { @transport.write(frame.encode) }
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
# Sends frame to the peer, checking that connection is open.
|
553
|
+
# Uses transport implementation that does not perform
|
554
|
+
# timeout control. Exposed primarily for Bunny::Channel.
|
555
|
+
#
|
556
|
+
# @raise [ConnectionClosedError]
|
557
|
+
# @private
|
558
|
+
def send_frame_without_timeout(frame)
|
559
|
+
if closed?
|
560
|
+
raise ConnectionClosedError.new(frame)
|
561
|
+
else
|
562
|
+
@network_mutex.synchronize { @transport.write_without_timeout(frame.encode) }
|
535
563
|
end
|
536
564
|
end
|
537
565
|
|
@@ -546,11 +574,27 @@ module Bunny
|
|
546
574
|
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
547
575
|
# locking. Note that "single frame" methods do not need this kind of synchronization. MK.
|
548
576
|
channel.synchronize do
|
549
|
-
frames.each { |frame|
|
577
|
+
frames.each { |frame| self.send_frame(frame) }
|
550
578
|
@transport.flush
|
551
579
|
end
|
552
580
|
end # send_frameset(frames)
|
553
581
|
|
582
|
+
# Sends multiple frames, one by one. For thread safety this method takes a channel
|
583
|
+
# object and synchronizes on it. Uses transport implementation that does not perform
|
584
|
+
# timeout control.
|
585
|
+
#
|
586
|
+
# @api private
|
587
|
+
def send_frameset_without_timeout(frames, channel)
|
588
|
+
# some developers end up sharing channels between threads and when multiple
|
589
|
+
# threads publish on the same channel aggressively, at some point frames will be
|
590
|
+
# delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception.
|
591
|
+
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
592
|
+
# locking. Note that "single frame" methods do not need this kind of synchronization. MK.
|
593
|
+
channel.synchronize do
|
594
|
+
frames.each { |frame| self.send_frame_without_timeout(frame) }
|
595
|
+
end
|
596
|
+
end # send_frameset_without_timeout(frames)
|
597
|
+
|
554
598
|
protected
|
555
599
|
|
556
600
|
# @api private
|
@@ -576,7 +620,7 @@ module Bunny
|
|
576
620
|
@transport.read_next_frame
|
577
621
|
# frame timeout means the broker has closed the TCP connection, which it
|
578
622
|
# does per 0.9.1 spec.
|
579
|
-
rescue Errno::ECONNRESET, ClientTimeout, AMQ::Protocol::EmptyResponseError, EOFError => e
|
623
|
+
rescue Errno::ECONNRESET, ClientTimeout, AMQ::Protocol::EmptyResponseError, EOFError, IOError => e
|
580
624
|
nil
|
581
625
|
end
|
582
626
|
if frame.nil?
|
@@ -644,7 +688,7 @@ module Bunny
|
|
644
688
|
|
645
689
|
# @api private
|
646
690
|
def initialize_transport
|
647
|
-
@transport = Transport.new(self, @host, @port, @opts)
|
691
|
+
@transport = Transport.new(self, @host, @port, @opts.merge(:session_thread => Thread.current))
|
648
692
|
end
|
649
693
|
|
650
694
|
# Sends AMQ protocol header (also known as preamble).
|