bunny 0.9.0.pre7 → 0.9.0.pre8
Sign up to get free protection for your applications and to get access to all the features.
- 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).
|