bunny 0.8.0 → 0.9.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.gitignore +7 -1
  2. data/.travis.yml +14 -4
  3. data/ChangeLog.md +72 -0
  4. data/Gemfile +17 -11
  5. data/README.md +82 -0
  6. data/bunny.gemspec +6 -13
  7. data/examples/connection/heartbeat.rb +17 -0
  8. data/lib/bunny.rb +40 -56
  9. data/lib/bunny/channel.rb +615 -19
  10. data/lib/bunny/channel_id_allocator.rb +59 -0
  11. data/lib/bunny/compatibility.rb +24 -0
  12. data/lib/bunny/concurrent/condition.rb +63 -0
  13. data/lib/bunny/consumer.rb +42 -26
  14. data/lib/bunny/consumer_tag_generator.rb +22 -0
  15. data/lib/bunny/consumer_work_pool.rb +67 -0
  16. data/lib/bunny/exceptions.rb +128 -0
  17. data/lib/bunny/exchange.rb +131 -136
  18. data/lib/bunny/framing.rb +53 -0
  19. data/lib/bunny/heartbeat_sender.rb +59 -0
  20. data/lib/bunny/main_loop.rb +70 -0
  21. data/lib/bunny/message_metadata.rb +126 -0
  22. data/lib/bunny/queue.rb +102 -275
  23. data/lib/bunny/session.rb +478 -0
  24. data/lib/bunny/socket.rb +44 -0
  25. data/lib/bunny/system_timer.rb +9 -9
  26. data/lib/bunny/transport.rb +179 -0
  27. data/lib/bunny/version.rb +1 -1
  28. data/spec/compatibility/queue_declare_spec.rb +40 -0
  29. data/spec/higher_level_api/integration/basic_ack_spec.rb +54 -0
  30. data/spec/higher_level_api/integration/basic_consume_spec.rb +51 -0
  31. data/spec/higher_level_api/integration/basic_get_spec.rb +47 -0
  32. data/spec/higher_level_api/integration/basic_nack_spec.rb +39 -0
  33. data/spec/higher_level_api/integration/basic_publish_spec.rb +105 -0
  34. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -0
  35. data/spec/higher_level_api/integration/basic_recover_spec.rb +18 -0
  36. data/spec/higher_level_api/integration/basic_reject_spec.rb +53 -0
  37. data/spec/higher_level_api/integration/basic_return_spec.rb +33 -0
  38. data/spec/higher_level_api/integration/channel_close_spec.rb +29 -0
  39. data/spec/higher_level_api/integration/channel_flow_spec.rb +24 -0
  40. data/spec/higher_level_api/integration/channel_open_spec.rb +57 -0
  41. data/spec/higher_level_api/integration/channel_open_stress_spec.rb +22 -0
  42. data/spec/higher_level_api/integration/confirm_select_spec.rb +19 -0
  43. data/spec/higher_level_api/integration/connection_spec.rb +340 -0
  44. data/spec/higher_level_api/integration/exchange_bind_spec.rb +31 -0
  45. data/spec/higher_level_api/integration/exchange_declare_spec.rb +183 -0
  46. data/spec/higher_level_api/integration/exchange_delete_spec.rb +37 -0
  47. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +40 -0
  48. data/spec/higher_level_api/integration/queue_bind_spec.rb +109 -0
  49. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -0
  50. data/spec/higher_level_api/integration/queue_delete_spec.rb +38 -0
  51. data/spec/higher_level_api/integration/queue_purge_spec.rb +30 -0
  52. data/spec/higher_level_api/integration/queue_unbind_spec.rb +33 -0
  53. data/spec/higher_level_api/integration/tx_commit_spec.rb +21 -0
  54. data/spec/higher_level_api/integration/tx_rollback_spec.rb +21 -0
  55. data/spec/lower_level_api/integration/basic_cancel_spec.rb +57 -0
  56. data/spec/lower_level_api/integration/basic_consume_spec.rb +100 -0
  57. data/spec/spec_helper.rb +64 -0
  58. data/spec/unit/bunny_spec.rb +15 -0
  59. data/spec/unit/concurrent/condition_spec.rb +66 -0
  60. metadata +135 -93
  61. data/CHANGELOG +0 -21
  62. data/README.textile +0 -76
  63. data/Rakefile +0 -14
  64. data/examples/simple.rb +0 -32
  65. data/examples/simple_ack.rb +0 -35
  66. data/examples/simple_consumer.rb +0 -55
  67. data/examples/simple_fanout.rb +0 -41
  68. data/examples/simple_headers.rb +0 -42
  69. data/examples/simple_publisher.rb +0 -29
  70. data/examples/simple_topic.rb +0 -61
  71. data/ext/amqp-0.9.1.json +0 -389
  72. data/ext/config.yml +0 -4
  73. data/ext/qparser.rb +0 -426
  74. data/lib/bunny/client.rb +0 -370
  75. data/lib/bunny/subscription.rb +0 -92
  76. data/lib/qrack/amq-client-url.rb +0 -165
  77. data/lib/qrack/channel.rb +0 -20
  78. data/lib/qrack/client.rb +0 -247
  79. data/lib/qrack/errors.rb +0 -5
  80. data/lib/qrack/protocol/protocol.rb +0 -135
  81. data/lib/qrack/protocol/spec.rb +0 -525
  82. data/lib/qrack/qrack.rb +0 -20
  83. data/lib/qrack/queue.rb +0 -40
  84. data/lib/qrack/subscription.rb +0 -152
  85. data/lib/qrack/transport/buffer.rb +0 -305
  86. data/lib/qrack/transport/frame.rb +0 -102
  87. data/spec/spec_09/amqp_url_spec.rb +0 -19
  88. data/spec/spec_09/bunny_spec.rb +0 -76
  89. data/spec/spec_09/connection_spec.rb +0 -34
  90. data/spec/spec_09/exchange_spec.rb +0 -173
  91. data/spec/spec_09/queue_spec.rb +0 -240
@@ -0,0 +1,44 @@
1
+ require "socket"
2
+
3
+ module Bunny
4
+ # TCP socket extension that uses TCP_NODELAY and supports reading
5
+ # fully.
6
+ #
7
+ # Heavily inspired by Dalli::Server::KSocket from Dalli by Mike Perham.
8
+ class Socket < TCPSocket
9
+ attr_accessor :options
10
+
11
+ def self.open(host, port, options = {})
12
+ Timeout.timeout(options[:socket_timeout]) do
13
+ sock = new(host, port)
14
+ if Socket.constants.include?('TCP_NODELAY') || Socket.constants.include?(:TCP_NODELAY)
15
+ sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
16
+ end
17
+ sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options[:keepalive]
18
+ sock.options = {:host => host, :port => port}.merge(options)
19
+ sock
20
+ end
21
+ end
22
+
23
+ def read_fully(count, timeout = nil)
24
+ return nil if @eof
25
+
26
+ value = ''
27
+ begin
28
+ loop do
29
+ value << read_nonblock(count - value.bytesize)
30
+ break if value.bytesize >= count
31
+ end
32
+ rescue EOFError
33
+ @eof = true
34
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
35
+ if IO.select([self], nil, nil, options.fetch(:socket_timeout, timeout))
36
+ retry
37
+ else
38
+ raise Timeout::Error, "IO timeout when reading #{count} bytes"
39
+ end
40
+ end
41
+ value
42
+ end
43
+ end
44
+ end
@@ -1,14 +1,14 @@
1
- # encoding: utf-8
1
+ # -*- encoding: utf-8; mode: ruby -*-
2
2
 
3
- module Bunny
4
- # Used for ruby < 1.9.x
5
- class SystemTimer < ::SystemTimer
3
+ require "system_timer"
6
4
 
7
- def timeout(seconds, exception)
8
- timeout_after(seconds) do
5
+ module Bunny
6
+ # Used for Ruby before 1.9
7
+ class SystemTimer
8
+ def self.timeout(seconds, exception)
9
+ ::SystemTimer.timeout_after(seconds) do
9
10
  yield
10
11
  end
11
12
  end
12
-
13
- end
14
- end
13
+ end # SystemTimer
14
+ end # Bunny
@@ -0,0 +1,179 @@
1
+ require "socket"
2
+ require "thread"
3
+
4
+ require "bunny/exceptions"
5
+ require "bunny/socket"
6
+
7
+ module Bunny
8
+ class Transport
9
+
10
+ #
11
+ # API
12
+ #
13
+
14
+ DEFAULT_CONNECTION_TIMEOUT = 5.0
15
+
16
+
17
+ attr_reader :host, :port, :socket, :connect_timeout
18
+
19
+ def initialize(host, port, opts)
20
+ @host = host
21
+ @port = port
22
+ @opts = opts
23
+
24
+ @ssl = opts[:ssl] || false
25
+ @ssl_cert = opts[:ssl_cert]
26
+ @ssl_key = opts[:ssl_key]
27
+ @ssl_cert_string = opts[:ssl_cert_string]
28
+ @ssl_key_string = opts[:ssl_key_string]
29
+ @verify_ssl = opts[:verify_ssl].nil? || opts[:verify_ssl]
30
+
31
+ @read_write_timeout = opts[:socket_timeout] || 1
32
+ @read_write_timeout = nil if @read_write_timeout == 0
33
+ @disconnect_timeout = @read_write_timeout || @connect_timeout
34
+ @connect_timeout = self.timeout_from(opts)
35
+
36
+ @frames = Hash.new { Array.new }
37
+
38
+ initialize_socket
39
+ end
40
+
41
+
42
+ def uses_tls?
43
+ @ssl
44
+ end
45
+ alias tls? uses_tls?
46
+
47
+ def uses_ssl?
48
+ @ssl
49
+ end
50
+ alias ssl? uses_ssl?
51
+
52
+
53
+
54
+ # Writes data to the socket. If read/write timeout was specified, Bunny::ClientTimeout will be raised
55
+ # if the operation times out.
56
+ #
57
+ # @raise [ClientTimeout]
58
+ def write(*args)
59
+ begin
60
+ raise Bunny::ConnectionError.new('No connection - socket has not been created', @host, @port) if !@socket
61
+ if @read_write_timeout
62
+ Bunny::Timer.timeout(@read_write_timeout, Bunny::ClientTimeout) do
63
+ @socket.write(*args)
64
+ end
65
+ else
66
+ @socket.write(*args)
67
+ end
68
+ rescue Errno::EPIPE, Errno::EAGAIN, Bunny::ClientTimeout, IOError => e
69
+ close!
70
+ raise Bunny::ConnectionError, e.message
71
+ end
72
+ end
73
+ alias send_raw write
74
+
75
+ def close(reason = nil)
76
+ @socket.close if @socket and not @socket.closed?
77
+ @socket = nil
78
+ end
79
+
80
+ def open?
81
+ !@socket.nil? && !@socket.closed?
82
+ end
83
+
84
+ def closed?
85
+ !open?
86
+ end
87
+
88
+ def flush
89
+ @socket.flush
90
+ end
91
+
92
+ def read_fully(*args)
93
+ @socket.read_fully(*args)
94
+ end
95
+
96
+ def read_ready?(timeout = nil)
97
+ io = IO.select([@socket].compact, nil, nil, timeout)
98
+ io && io[0].include?(@socket)
99
+ end
100
+
101
+
102
+ # Exposed primarily for Bunny::Channel
103
+ # @private
104
+ def read_next_frame(opts = {})
105
+ header = @socket.read_fully(7)
106
+ type, channel, size = AMQ::Protocol::Frame.decode_header(header)
107
+ payload = @socket.read_fully(size)
108
+ frame_end = @socket.read_fully(1)
109
+
110
+ # 1) the size is miscalculated
111
+ if payload.bytesize != size
112
+ raise BadLengthError.new(size, payload.bytesize)
113
+ end
114
+
115
+ # 2) the size is OK, but the string doesn't end with FINAL_OCTET
116
+ raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
117
+ AMQ::Protocol::Frame.new(type, payload, channel)
118
+ end
119
+
120
+
121
+ # Sends frame to the peer.
122
+ #
123
+ # @raise [ConnectionClosedError]
124
+ # @private
125
+ def send_frame(frame)
126
+ if closed?
127
+ raise ConnectionClosedError.new(frame)
128
+ else
129
+ send_raw(frame.encode)
130
+ end
131
+ end
132
+
133
+
134
+ protected
135
+
136
+ def initialize_socket
137
+ begin
138
+ @socket = Bunny::Timer.timeout(@connect_timeout, ConnectionTimeout) do
139
+ Bunny::Socket.open(@host, @port,
140
+ :keepalive => @opts[:keepalive],
141
+ :socket_timeout => @connect_timeout)
142
+ end
143
+
144
+ if @ssl
145
+ require 'openssl' unless defined? OpenSSL::SSL
146
+ sslctx = OpenSSL::SSL::SSLContext.new
147
+ initialize_client_pair(sslctx)
148
+ @socket = OpenSSL::SSL::SSLSocket.new(@socket, sslctx)
149
+ @socket.sync_close = true
150
+ @socket.connect
151
+ @socket.post_connection_check(host) if @verify_ssl
152
+ @socket
153
+ end
154
+ rescue Exception => e
155
+ @status = :not_connected
156
+ raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port)
157
+ end
158
+
159
+ @socket
160
+ end
161
+
162
+ def initialize_client_pair(sslctx)
163
+ if @ssl_cert
164
+ @ssl_cert_string = File.read(@ssl_cert)
165
+ end
166
+ if @ssl_key
167
+ @ssl_key_string = File.read(@ssl_key)
168
+ end
169
+
170
+ sslctx.cert = OpenSSL::X509::Certificate.new(@ssl_cert_string) if @ssl_cert_string
171
+ sslctx.key = OpenSSL::PKey::RSA.new(@ssl_key_string) if @ssl_key_string
172
+ sslctx
173
+ end
174
+
175
+ def timeout_from(options)
176
+ options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT
177
+ end
178
+ end
179
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Bunny
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.0.pre1"
5
5
  end
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ describe Bunny::Queue, "backwards compatibility API" do
4
+ let(:connection) do
5
+ c = Bunny.new
6
+ c.start
7
+ c
8
+ end
9
+
10
+ context "when queue name is specified" do
11
+ let(:name) { "a queue declared at #{Time.now.to_i}" }
12
+
13
+ it "declares a new queue with that name" do
14
+ q = Bunny::Queue.new(connection, name)
15
+ q.name.should == name
16
+ end
17
+ end
18
+
19
+
20
+ context "when queue name is passed on as an empty string" do
21
+ it "uses server-assigned queue name" do
22
+ q = Bunny::Queue.new(connection, "")
23
+ q.name.should_not be_empty
24
+ q.name.should =~ /^amq.gen.+/
25
+ q.should be_server_named
26
+ q.delete
27
+ end
28
+ end
29
+
30
+
31
+ context "when queue name is completely omitted" do
32
+ it "uses server-assigned queue name" do
33
+ q = Bunny::Queue.new(connection)
34
+ q.name.should_not be_empty
35
+ q.name.should =~ /^amq.gen.+/
36
+ q.should be_server_named
37
+ q.delete
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe Bunny::Channel, "#ack" do
4
+ let(:connection) do
5
+ c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
6
+ c.start
7
+ c
8
+ end
9
+
10
+ after :all do
11
+ connection.close if connection.open?
12
+ end
13
+
14
+ subject do
15
+ connection.create_channel
16
+ end
17
+
18
+ context "with a valid (known) delivery tag" do
19
+ it "acknowleges a message" do
20
+ q = subject.queue("bunny.basic.ack.manual-acks", :exclusive => true)
21
+ x = subject.default_exchange
22
+
23
+ x.publish("bunneth", :routing_key => q.name)
24
+ sleep(0.25)
25
+ q.message_count.should == 1
26
+ delivery_details, properties, content = q.pop(:ack => true)
27
+
28
+ subject.ack(delivery_details.delivery_tag, true)
29
+ sleep(0.25)
30
+ q.message_count.should == 0
31
+
32
+ subject.close
33
+ end
34
+ end
35
+
36
+ context "with an invalid (random) delivery tag" do
37
+ it "causes a channel-level error" do
38
+ pending "We need to design async channel error handling for cases when there is no reply methods (e.g. basic.ack)"
39
+
40
+ q = subject.queue("bunny.basic.ack.unknown-delivery-tag", :exclusive => true)
41
+ x = subject.default_exchange
42
+
43
+ x.publish("bunneth", :routing_key => q.name)
44
+ sleep(0.25)
45
+ q.message_count.should == 1
46
+ _, _, content = q.pop(:ack => true)
47
+
48
+ subject.on_error do |ch, channel_close|
49
+ @channel_close = channel_close
50
+ end
51
+ # subject.ack(82, true)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ describe Bunny::Queue, "#subscribe" do
4
+ let(:connection) do
5
+ c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
6
+ c.start
7
+ c
8
+ end
9
+
10
+ after :all do
11
+ connection.close if connection.open?
12
+ end
13
+
14
+ context "with automatic acknowledgement mode" do
15
+ let(:queue_name) { "bunny.basic_consume#{rand}" }
16
+
17
+ it "registers the consumer" do
18
+ delivered_keys = []
19
+ delivered_data = []
20
+
21
+ t = Thread.new do
22
+ ch = connection.create_channel
23
+ q = ch.queue(queue_name, :auto_delete => true, :durable => false)
24
+ q.subscribe(:exclusive => false, :ack => false) do |metadata, payload|
25
+ delivered_keys << metadata.routing_key
26
+ delivered_data << payload
27
+ end
28
+ end
29
+ t.abort_on_exception = true
30
+ sleep 0.5
31
+
32
+ ch = connection.create_channel
33
+ x = ch.default_exchange
34
+ x.publish("hello", :routing_key => queue_name)
35
+
36
+ sleep 0.7
37
+ delivered_keys.should include(queue_name)
38
+ delivered_data.should include("hello")
39
+
40
+ ch.queue(queue_name, :auto_delete => true, :durable => false).message_count.should == 0
41
+
42
+ ch.close
43
+ end
44
+ end
45
+
46
+ context "with manual acknowledgement mode" do
47
+ let(:queue_name) { "bunny.basic_consume#{rand}" }
48
+
49
+ it "register a consumer with manual acknowledgements mode"
50
+ end
51
+ end
@@ -0,0 +1,47 @@
1
+ require "spec_helper"
2
+
3
+ describe Bunny::Queue, "#pop" do
4
+ let(:connection) do
5
+ c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
6
+ c.start
7
+ c
8
+ end
9
+
10
+ after :all do
11
+ connection.close if connection.open?
12
+ end
13
+
14
+ context "with all defaults" do
15
+ it "fetches a messages which is automatically acknowledged" do
16
+ ch = connection.create_channel
17
+
18
+ q = ch.queue("", :exclusive => true)
19
+ x = ch.default_exchange
20
+
21
+ x.publish("xyzzy", :routing_key => q.name)
22
+
23
+ sleep(0.5)
24
+ delivery_info, properties, content = q.pop
25
+ content.should == "xyzzy"
26
+ q.message_count.should == 0
27
+
28
+ ch.close
29
+ end
30
+ end
31
+
32
+
33
+ context "with an empty queue" do
34
+ it "returns an empty response" do
35
+ ch = connection.create_channel
36
+
37
+ q = ch.queue("", :exclusive => true)
38
+ q.purge
39
+
40
+ _, _, content = q.pop
41
+ content.should be_nil
42
+ q.message_count.should == 0
43
+
44
+ ch.close
45
+ end
46
+ end
47
+ end