amq-client 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/.gitignore +8 -0
  2. data/.gitmodules +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +7 -0
  5. data/.yardopts +1 -0
  6. data/CONTRIBUTORS +3 -0
  7. data/Gemfile +27 -0
  8. data/LICENSE +20 -0
  9. data/README.textile +61 -0
  10. data/amq-client.gemspec +34 -0
  11. data/bin/jenkins.sh +23 -0
  12. data/bin/set_test_suite_realms_up.sh +24 -0
  13. data/examples/coolio_adapter/basic_consume.rb +49 -0
  14. data/examples/coolio_adapter/basic_consume_with_acknowledgements.rb +43 -0
  15. data/examples/coolio_adapter/basic_consume_with_rejections.rb +43 -0
  16. data/examples/coolio_adapter/basic_publish.rb +35 -0
  17. data/examples/coolio_adapter/channel_close.rb +24 -0
  18. data/examples/coolio_adapter/example_helper.rb +39 -0
  19. data/examples/coolio_adapter/exchange_declare.rb +28 -0
  20. data/examples/coolio_adapter/kitchen_sink1.rb +48 -0
  21. data/examples/coolio_adapter/queue_bind.rb +32 -0
  22. data/examples/coolio_adapter/queue_purge.rb +32 -0
  23. data/examples/coolio_adapter/queue_unbind.rb +37 -0
  24. data/examples/eventmachine_adapter/authentication/plain_password_with_custom_role_credentials.rb +36 -0
  25. data/examples/eventmachine_adapter/authentication/plain_password_with_default_role_credentials.rb +27 -0
  26. data/examples/eventmachine_adapter/authentication/plain_password_with_incorrect_credentials.rb +18 -0
  27. data/examples/eventmachine_adapter/basic_cancel.rb +49 -0
  28. data/examples/eventmachine_adapter/basic_consume.rb +51 -0
  29. data/examples/eventmachine_adapter/basic_consume_with_acknowledgements.rb +45 -0
  30. data/examples/eventmachine_adapter/basic_consume_with_rejections.rb +45 -0
  31. data/examples/eventmachine_adapter/basic_get.rb +57 -0
  32. data/examples/eventmachine_adapter/basic_get_with_empty_queue.rb +53 -0
  33. data/examples/eventmachine_adapter/basic_publish.rb +38 -0
  34. data/examples/eventmachine_adapter/basic_qos.rb +29 -0
  35. data/examples/eventmachine_adapter/basic_recover.rb +29 -0
  36. data/examples/eventmachine_adapter/basic_return.rb +34 -0
  37. data/examples/eventmachine_adapter/channel_close.rb +24 -0
  38. data/examples/eventmachine_adapter/channel_flow.rb +36 -0
  39. data/examples/eventmachine_adapter/channel_level_exception_handling.rb +44 -0
  40. data/examples/eventmachine_adapter/example_helper.rb +39 -0
  41. data/examples/eventmachine_adapter/exchange_declare.rb +54 -0
  42. data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +31 -0
  43. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +56 -0
  44. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +46 -0
  45. data/examples/eventmachine_adapter/kitchen_sink1.rb +50 -0
  46. data/examples/eventmachine_adapter/queue_bind.rb +32 -0
  47. data/examples/eventmachine_adapter/queue_declare.rb +34 -0
  48. data/examples/eventmachine_adapter/queue_purge.rb +32 -0
  49. data/examples/eventmachine_adapter/queue_unbind.rb +37 -0
  50. data/examples/eventmachine_adapter/tx_commit.rb +29 -0
  51. data/examples/eventmachine_adapter/tx_rollback.rb +29 -0
  52. data/examples/eventmachine_adapter/tx_select.rb +27 -0
  53. data/examples/socket_adapter/basics.rb +19 -0
  54. data/examples/socket_adapter/connection.rb +53 -0
  55. data/examples/socket_adapter/multiple_connections.rb +17 -0
  56. data/irb.rb +66 -0
  57. data/lib/amq/client.rb +15 -0
  58. data/lib/amq/client/adapter.rb +356 -0
  59. data/lib/amq/client/adapters/coolio.rb +221 -0
  60. data/lib/amq/client/adapters/event_machine.rb +228 -0
  61. data/lib/amq/client/adapters/socket.rb +89 -0
  62. data/lib/amq/client/channel.rb +338 -0
  63. data/lib/amq/client/connection.rb +246 -0
  64. data/lib/amq/client/entity.rb +117 -0
  65. data/lib/amq/client/exceptions.rb +86 -0
  66. data/lib/amq/client/exchange.rb +163 -0
  67. data/lib/amq/client/extensions/rabbitmq.rb +5 -0
  68. data/lib/amq/client/extensions/rabbitmq/basic.rb +36 -0
  69. data/lib/amq/client/extensions/rabbitmq/confirm.rb +254 -0
  70. data/lib/amq/client/framing/io/frame.rb +32 -0
  71. data/lib/amq/client/framing/string/frame.rb +62 -0
  72. data/lib/amq/client/logging.rb +56 -0
  73. data/lib/amq/client/mixins/anonymous_entity.rb +21 -0
  74. data/lib/amq/client/mixins/status.rb +62 -0
  75. data/lib/amq/client/protocol/get_response.rb +55 -0
  76. data/lib/amq/client/queue.rb +450 -0
  77. data/lib/amq/client/settings.rb +83 -0
  78. data/lib/amq/client/version.rb +5 -0
  79. data/spec/benchmarks/adapters.rb +77 -0
  80. data/spec/client/framing/io_frame_spec.rb +57 -0
  81. data/spec/client/framing/string_frame_spec.rb +57 -0
  82. data/spec/client/protocol/get_response_spec.rb +79 -0
  83. data/spec/integration/coolio/basic_ack_spec.rb +41 -0
  84. data/spec/integration/coolio/basic_get_spec.rb +73 -0
  85. data/spec/integration/coolio/basic_return_spec.rb +33 -0
  86. data/spec/integration/coolio/channel_close_spec.rb +26 -0
  87. data/spec/integration/coolio/channel_flow_spec.rb +46 -0
  88. data/spec/integration/coolio/spec_helper.rb +31 -0
  89. data/spec/integration/coolio/tx_commit_spec.rb +40 -0
  90. data/spec/integration/coolio/tx_rollback_spec.rb +44 -0
  91. data/spec/integration/eventmachine/basic_ack_spec.rb +40 -0
  92. data/spec/integration/eventmachine/basic_get_spec.rb +73 -0
  93. data/spec/integration/eventmachine/basic_return_spec.rb +35 -0
  94. data/spec/integration/eventmachine/channel_close_spec.rb +26 -0
  95. data/spec/integration/eventmachine/channel_flow_spec.rb +32 -0
  96. data/spec/integration/eventmachine/spec_helper.rb +22 -0
  97. data/spec/integration/eventmachine/tx_commit_spec.rb +47 -0
  98. data/spec/integration/eventmachine/tx_rollback_spec.rb +35 -0
  99. data/spec/regression/bad_frame_slicing_in_adapters_spec.rb +59 -0
  100. data/spec/spec_helper.rb +24 -0
  101. data/spec/unit/client/adapter_spec.rb +49 -0
  102. data/spec/unit/client/entity_spec.rb +49 -0
  103. data/spec/unit/client/logging_spec.rb +60 -0
  104. data/spec/unit/client/mixins/status_spec.rb +72 -0
  105. data/spec/unit/client/settings_spec.rb +27 -0
  106. data/spec/unit/client_spec.rb +11 -0
  107. data/tasks.rb +11 -0
  108. metadata +202 -0
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/mixins/status"
4
+
5
+ module AMQ
6
+ module Client
7
+ # AMQ entities, as implemented by AMQ::Client, have callbacks and can run them
8
+ # when necessary.
9
+ #
10
+ # @note Exchanges and queues implementation is based on this class.
11
+ #
12
+ # @abstract
13
+ class Entity
14
+
15
+ #
16
+ # Behaviors
17
+ #
18
+
19
+ include StatusMixin
20
+
21
+ #
22
+ # API
23
+ #
24
+
25
+ # @return [Array<#call>]
26
+ attr_reader :callbacks
27
+
28
+ @@handlers ||= Hash.new
29
+
30
+ def self.handle(klass, &block)
31
+ @@handlers[klass] = block
32
+ end
33
+
34
+ def self.handlers
35
+ @@handlers
36
+ end
37
+
38
+
39
+
40
+
41
+ def initialize(client)
42
+ @client = client
43
+ # Be careful with default values for #ruby hashes: h = Hash.new(Array.new); h[:key] ||= 1
44
+ # won't assign anything to :key. MK.
45
+ @callbacks = Hash.new
46
+ end
47
+
48
+
49
+ def redefine_callback(event, callable = nil, &block)
50
+ f = (callable || block)
51
+ # yes, re-assign!
52
+ @callbacks[event] = [f]
53
+
54
+ self
55
+ end
56
+
57
+ def define_callback(event, callable = nil, &block)
58
+ f = (callable || block)
59
+
60
+ @callbacks[event] ||= []
61
+ @callbacks[event] << f if f
62
+
63
+ self
64
+ end # define_callback(event, &block)
65
+ alias append_callback define_callback
66
+
67
+ def prepend_callback(event, &block)
68
+ @callbacks[event] ||= []
69
+ @callbacks[event].unshift(block)
70
+
71
+ self
72
+ end # prepend_callback(event, &block)
73
+
74
+ def clear_callbacks(event)
75
+ @callbacks[event].clear if @callbacks[event]
76
+ end # clear_callbacks(event)
77
+
78
+
79
+ def exec_callback(name, *args, &block)
80
+ callbacks = Array(self.callbacks[name])
81
+ callbacks.map { |c| c.call(*args, &block) } if callbacks.any?
82
+ end
83
+
84
+ def exec_callback_once(name, *args, &block)
85
+ callbacks = Array(self.callbacks.delete(name))
86
+ callbacks.map { |c| c.call(*args, &block) } if callbacks.any?
87
+ end
88
+
89
+ def exec_callback_yielding_self(name, *args, &block)
90
+ callbacks = Array(self.callbacks[name])
91
+ callbacks.map { |c| c.call(self, *args, &block) } if callbacks.any?
92
+ end
93
+
94
+ def exec_callback_once_yielding_self(name, *args, &block)
95
+ callbacks = Array(self.callbacks.delete(name))
96
+ callbacks.map { |c| c.call(self, *args, &block) } if callbacks.any?
97
+ end
98
+
99
+
100
+
101
+ def error(exception)
102
+ if client.sync? # DO NOT DO THIS, just add a default errback to do exactly this, so if someone wants to use begin/rescue, he'll just ignore the errbacks.
103
+ # Synchronous error handling.
104
+ # Just use begin/rescue in the main loop.
105
+ raise exception
106
+ else
107
+ # Asynchronous error handling.
108
+ # Set callback for given class (Queue for example)
109
+ # or for the Connection class (or instance, of course).
110
+ callbacks = [self.callbacks[:close], self.client.connection.callbacks[:close]].flatten.compact
111
+
112
+ callbacks.map { |c| c.call(exception) } if callbacks.any?
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+
3
+ module AMQ
4
+ module Client
5
+
6
+ #
7
+ # Adapters
8
+ #
9
+
10
+ # Base exception class for data consistency and framing errors.
11
+ class InconsistentDataError < StandardError
12
+ end
13
+
14
+ # Raised by adapters when frame does not end with {final octet AMQ::Protocol::Frame::FINAL_OCTET}.
15
+ # This suggest that there is a bug in adapter or AMQ broker implementation.
16
+ #
17
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.3)
18
+ class NoFinalOctetError < InconsistentDataError
19
+ def initialize
20
+ super("Frame doesn't end with #{AMQ::Protocol::Frame::FINAL_OCTET} as it must, which means the size is miscalculated.")
21
+ end
22
+ end
23
+
24
+ # Raised by adapters when actual frame payload size in bytes is not equal
25
+ # to the size specified in that frame's header.
26
+ # This suggest that there is a bug in adapter or AMQ broker implementation.
27
+ #
28
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.3)
29
+ class BadLengthError < InconsistentDataError
30
+ def initialize(expected_length, actual_length)
31
+ super("Frame payload should be #{expected_length} long, but it's #{actual_length} long.")
32
+ end
33
+ end
34
+
35
+ #
36
+ # Client
37
+ #
38
+
39
+ class MissingInterfaceMethodError < NotImplementedError
40
+ def initialize(method_name)
41
+ super("Method #{method_name} is supposed to be overriden by adapter")
42
+ end
43
+ end
44
+
45
+ class MissingHandlerError < StandardError
46
+ def initialize(frame)
47
+ super("No callback registered for #{frame.method_class}")
48
+ end
49
+ end
50
+
51
+ class ConnectionClosedError < StandardError
52
+ def initialize(frame)
53
+ if frame.respond_to?(:method_class)
54
+ super("Trying to send frame through a closed connection. Frame is #{frame.inspect}")
55
+ else
56
+ super("Trying to send frame through a closed connection. Frame is #{frame.inspect}, method class is #{frame.method_class}")
57
+ end
58
+ end # initialize
59
+ end # class ConnectionClosedError
60
+
61
+ module Logging
62
+ # Raised when logger object passed to {AMQ::Client::Adapter.logger=} does not
63
+ # provide API it supposed to provide.
64
+ #
65
+ # @see AMQ::Client::Adapter.logger=
66
+ class IncompatibleLoggerError < StandardError
67
+ def initialize(required_methods)
68
+ super("Logger has to respond to the following methods: #{required_methods.inspect}")
69
+ end
70
+ end
71
+ end # Logging
72
+
73
+
74
+ class PossibleAuthenticationFailureError < StandardError
75
+
76
+ #
77
+ # API
78
+ #
79
+
80
+ def initialize(settings)
81
+ super("AMQP broker closed TCP connection before authentication succeeded: this usually means authentication failure due to misconfiguration. Settings are #{settings.inspect}")
82
+ end # initialize(settings)
83
+ end # PossibleAuthenticationFailureError
84
+
85
+ end # Client
86
+ end # AMQ
@@ -0,0 +1,163 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/entity"
4
+ require "amq/client/mixins/anonymous_entity"
5
+
6
+ module AMQ
7
+ module Client
8
+ class Exchange < Entity
9
+ include AnonymousEntityMixin
10
+
11
+ TYPES = [:fanout, :direct, :topic, :headers].freeze
12
+
13
+ class IncompatibleExchangeTypeError < StandardError
14
+ def initialize(types, given)
15
+ super("Exchange types are #{TYPES.inspect}, #{given.inspect} given.")
16
+ end
17
+ end
18
+
19
+
20
+ #
21
+ # API
22
+ #
23
+
24
+ # Channel this exchange belongs to.
25
+ attr_reader :channel
26
+
27
+ # Exchange name. May be server-generated or assigned directly.
28
+ attr_reader :name
29
+
30
+ # @return [Symbol] One of :direct, :fanout, :topic, :headers
31
+ attr_reader :type
32
+
33
+ def initialize(client, channel, name, type = :fanout)
34
+ unless TYPES.include?(type.to_sym)
35
+ raise IncompatibleExchangeTypeError.new(TYPES, type)
36
+ end
37
+
38
+ @client = client
39
+ @channel = channel
40
+ @name = name
41
+ @type = type
42
+
43
+ # register pre-declared exchanges
44
+ if @name == AMQ::Protocol::EMPTY_STRING || @name =~ /^amq\.(fanout|topic)/
45
+ @channel.register_exchange(self)
46
+ end
47
+
48
+ super(client)
49
+ end
50
+
51
+
52
+ def fanout?
53
+ @type == :fanout
54
+ end
55
+
56
+ def direct?
57
+ @type == :direct
58
+ end
59
+
60
+ def topic?
61
+ @type == :topic
62
+ end
63
+
64
+
65
+
66
+ def declare(passive = false, durable = false, exclusive = false, auto_delete = false, nowait = false, arguments = nil, &block)
67
+ @client.send(Protocol::Exchange::Declare.encode(@channel.id, @name, @type.to_s, passive, durable, auto_delete, false, nowait, arguments))
68
+
69
+ unless nowait
70
+ self.define_callback(:declare, &block)
71
+ @channel.exchanges_awaiting_declare_ok.push(self)
72
+ end
73
+
74
+
75
+ if @client.sync?
76
+ @client.read_until_receives(Protocol::Exchange::DeclareOk) unless nowait
77
+ end
78
+
79
+ self
80
+ end
81
+
82
+
83
+ def delete(if_unused = false, nowait = false, &block)
84
+ @client.send(Protocol::Exchange::Delete.encode(@channel.id, @name, if_unused, nowait))
85
+
86
+ unless nowait
87
+ self.define_callback(:delete, &block)
88
+
89
+ # TODO: delete itself from exchanges cache
90
+ @channel.exchanges_awaiting_delete_ok.push(self)
91
+ end
92
+
93
+ self
94
+ end # delete(if_unused = false, nowait = false)
95
+
96
+
97
+ def publish(payload, routing_key = AMQ::Protocol::EMPTY_STRING, user_headers = {}, mandatory = false, immediate = false, frame_size = nil, &block)
98
+ headers = { :priority => 0, :delivery_mode => 2, :content_type => "application/octet-stream" }.merge(user_headers)
99
+ @client.send_frameset(Protocol::Basic::Publish.encode(@channel.id, payload, headers, @name, routing_key, mandatory, immediate, (frame_size || @client.connection.frame_max)))
100
+
101
+ block.call if block
102
+
103
+ self
104
+ end
105
+
106
+
107
+ def on_return(&block)
108
+ self.redefine_callback(:return, &block)
109
+
110
+ self
111
+ end # on_return(&block)
112
+
113
+
114
+
115
+
116
+ def handle_declare_ok(method)
117
+ @name = method.exchange if self.anonymous?
118
+ @channel.register_exchange(self)
119
+
120
+ self.exec_callback_once_yielding_self(:declare, method)
121
+ end
122
+
123
+ def handle_delete_ok(method)
124
+ self.exec_callback_once(:delete, method)
125
+ end # handle_delete_ok(method)
126
+
127
+
128
+
129
+ # === Handlers ===
130
+ # Get the first exchange which didn't receive Exchange.Declare-Ok yet and run its declare callback.
131
+ # The cache includes only exchanges with {nowait: false}.
132
+ self.handle(Protocol::Exchange::DeclareOk) do |client, frame|
133
+ method = frame.decode_payload
134
+
135
+ # We should have cache API, so it'll be easy to change caching behaviour easily.
136
+ # So in the amq-client we don't want to cache more than just the last instance per each channel,
137
+ # whereas more opinionated clients might want to have every single instance in the cache,
138
+ # so they can iterate over it etc.
139
+ channel = client.connection.channels[frame.channel]
140
+ exchange = channel.exchanges_awaiting_declare_ok.shift
141
+
142
+ exchange.handle_declare_ok(method)
143
+ end # handle
144
+
145
+
146
+ self.handle(Protocol::Exchange::DeleteOk) do |client, frame|
147
+ channel = client.connection.channels[frame.channel]
148
+ exchange = channel.exchanges_awaiting_delete_ok.shift
149
+ exchange.handle_delete_ok(frame.decode_payload)
150
+ end # handle
151
+
152
+
153
+ self.handle(Protocol::Basic::Return) do |client, frame|
154
+ channel = client.connection.channels[frame.channel]
155
+ method = frame.decode_payload
156
+ exchange = channel.find_exchange(method.exchange)
157
+
158
+ exchange.exec_callback(:return, method)
159
+ end
160
+
161
+ end # Exchange
162
+ end # Client
163
+ end # AMQ
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ # http://www.rabbitmq.com/extensions.html
4
+ require "amq/client/extensions/rabbitmq/basic"
5
+ require "amq/client/extensions/rabbitmq/confirm"
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/channel"
4
+
5
+ # Basic.Nack
6
+ module AMQ
7
+ module Client
8
+ module Extensions
9
+ module RabbitMQ
10
+ module Basic
11
+ module ChannelMixin
12
+
13
+ # Overrides {AMQ::Client::Channel#reject} behavior to use basic.nack.
14
+ #
15
+ # @api public
16
+ # @see http://www.rabbitmq.com/amqp-0-9-1-quickref.html#basic.nack
17
+ def reject(delivery_tag, requeue = true, multi = false)
18
+ if multi
19
+ @client.send(Protocol::Basic::Nack.encode(self.id, delivery_tag, multi, requeue))
20
+ else
21
+ super(delivery_tag, requeue)
22
+ end
23
+ end # reject
24
+
25
+ end # ChannelMixin
26
+ end # Basic
27
+ end # RabbitMQ
28
+ end # Extensions
29
+
30
+ class Channel
31
+ # use modules, a native Ruby way of extension of existing classes,
32
+ # instead of reckless monkey-patching. MK.
33
+ include Extensions::RabbitMQ::Basic::ChannelMixin
34
+ end
35
+ end # Client
36
+ end # AMQ
@@ -0,0 +1,254 @@
1
+ # encoding: utf-8
2
+
3
+ # === Purpose === #
4
+ # In case that the broker crashes, some messages can get lost.
5
+ # Thanks to this extension, broker sends Basic.Ack when the message
6
+ # is processed by the broker. In case of persistent messages, it must
7
+ # be written to disk or ack'd on all the queues it was delivered to.
8
+ # However it doesn't have to be necessarily 1:1, because the broker
9
+ # can send Basic.Ack with multi flag to acknowledge multiple messages.
10
+ #
11
+ # So it provides clients a lightweight way of keeping track of which
12
+ # messages have been processed by the broker and which would need
13
+ # re-publishing in case of broker shutdown or network failure.
14
+ #
15
+ # Transactions are solving the same problem, but they are very slow:
16
+ # confirmations are more than 100 times faster.
17
+ #
18
+ # === Workflow === #
19
+ # * Client asks broker to confirm messages on given channel (Confirm.Select).
20
+ # * Broker sends back Confirm.Select-Ok, unless we sent Confirm.Select with nowait=true.
21
+ # * After each published message, the client receives Basic.Ack from the broker.
22
+ # * If something bad happens inside the broker, it sends Basic.Nack.
23
+ #
24
+ # === Gotchas === #
25
+ # Note that we don't keep track of messages awaiting confirmation.
26
+ # It'd add a huge overhead and it's impossible to come up with one-suits-all solution.
27
+ # If you want to create such module, you'll probably want to redefine Channel#after_publish,
28
+ # so it will put messages into a queue and then handlers for Basic.Ack and Basic.Nack.
29
+ # This is the reason why we pass every argument from Exchange#publish to Channel#after_publish.
30
+ # You should not forget though, that both of these methods can have multi flag!
31
+ #
32
+ # Transactional channel cannot be put into confirm mode and a confirm
33
+ # mode channel cannot be made transactional.
34
+ #
35
+ # If the connection between the publisher and broker drops with outstanding
36
+ # confirms, it does not necessarily mean that the messages were lost, so
37
+ # republishing may result in duplicate messages.
38
+
39
+ # === Links === #
40
+ # http://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms
41
+ # http://www.rabbitmq.com/amqp-0-9-1-quickref.html#class.confirm
42
+ # http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.ack
43
+
44
+ puts "in confirm.rb"
45
+
46
+ module AMQ
47
+ module Client
48
+ module Extensions
49
+ module RabbitMQ
50
+ module Confirm
51
+ module ChannelMixin
52
+ # Boolean value expressing whether confirmations are
53
+ # on or off, aka whether Confirm.Select was sent or not.
54
+ #
55
+ # @api public
56
+ # @return [Boolean] Whether confirmations are on or off.
57
+ attr_reader :confirmations
58
+
59
+ # Change publisher index. Publisher index is incremented
60
+ # by 1 after each Basic.Publish starting at 1. This is done
61
+ # on both client and server, hence this acknowledged messages
62
+ # can be matched via its delivery-tag.
63
+ #
64
+ # @api private
65
+ attr_writer :publisher_index
66
+
67
+ # Publisher index is an index of the last message since
68
+ # the confirmations were activated, started with 1. It's
69
+ # incremented by 1 after each Basic.Publish starting at 1.
70
+ # This is done on both client and server, hence this
71
+ # acknowledged messages can be matched via its delivery-tag.
72
+ #
73
+ # @return [Integer] Current publisher index.
74
+ # @api public
75
+ def publisher_index
76
+ @publisher_index ||= 1
77
+ end
78
+
79
+ # Resets publisher index to 0
80
+ #
81
+ # @api plugin
82
+ def reset_publisher_index!
83
+ @publisher_index = 0
84
+ end
85
+
86
+
87
+ # This method is executed after publishing of each message via {Exchage#publish}.
88
+ # Currently it just increments publisher index by 1, so messages
89
+ # can be actually matched.
90
+ #
91
+ # @api plugin
92
+ def after_publish(*args)
93
+ self.publisher_index += 1
94
+ end
95
+
96
+ # Turn on confirmations for this channel and, if given,
97
+ # register callback for Confirm.Select-Ok.
98
+ #
99
+ # @raise [RuntimeError] Occurs when confirmations are already activated.
100
+ # @raise [RuntimeError] Occurs when nowait is true and block is given.
101
+ #
102
+ # @param [Boolean] nowait Whether we expect Confirm.Select-Ok to be returned by the broker or not.
103
+ # @yield [method] Callback which will be executed once we receive Confirm.Select-Ok.
104
+ # @yieldparam [AMQ::Protocol::Confirm::SelectOk] method Protocol method class instance.
105
+ #
106
+ # @return [self] self.
107
+ #
108
+ # @see #confirm
109
+ def confirmations(nowait = false, &block)
110
+ if @confirmations
111
+ raise "Confirmations are already activated!"
112
+ end
113
+
114
+ if nowait && block
115
+ raise "You can't use Confirm.Select with nowait=true and a callback at the same time."
116
+ end
117
+
118
+ @confirmations = true
119
+ self.redefine_callback(:confirmations, &block)
120
+ @client.send(Protocol::Confirm::Select.encode(@id, nowait))
121
+
122
+ self
123
+ end
124
+
125
+
126
+ # Turn on confirmations for this channel and, if given,
127
+ # register callback for basic.ack from the broker.
128
+ #
129
+ # @raise [RuntimeError] Occurs when confirmations are already activated.
130
+ # @raise [RuntimeError] Occurs when nowait is true and block is given.
131
+ # @param [Boolean] nowait Whether we expect Confirm.Select-Ok to be returned by the broker or not.
132
+ #
133
+ # @yield [basick_ack] Callback which will be executed every time we receive Basic.Ack from the broker.
134
+ # @yieldparam [AMQ::Protocol::Basic::Ack] basick_ack Protocol method class instance.
135
+ #
136
+ # @return [self] self.
137
+ def confirm(nowait = false, &block)
138
+ self.confirmations unless @confirmations
139
+
140
+ self.define_callback(:ack, &block) if block
141
+
142
+ self
143
+ end
144
+
145
+
146
+ # Register error callback for Basic.Nack. It's called
147
+ # when the broker reject given message(s).
148
+ #
149
+ # @return [self] self
150
+ def confirm_failed(&block)
151
+ self.define_callback(:nack, &block) if block
152
+
153
+ self
154
+ end
155
+
156
+
157
+
158
+
159
+ # Handler for Confirm.Select-Ok. By default, it just
160
+ # executes hook specified via the #confirmations method
161
+ # with a single argument, a protocol method class
162
+ # instance (an instance of AMQ::Protocol::Confirm::SelectOk)
163
+ # and then it deletes the callback, since Confirm.Select
164
+ # is supposed to be sent just once.
165
+ #
166
+ # @api plugin
167
+ def handle_select_ok(method)
168
+ self.exec_callback_once(:confirmations, method)
169
+ end
170
+
171
+ # Handler for Basic.Ack. By default, it just
172
+ # executes hook specified via the #confirm method
173
+ # with a single argument, a protocol method class
174
+ # instance (an instance of AMQ::Protocol::Basic::Ack).
175
+ #
176
+ # @api plugin
177
+ def handle_basic_ack(method)
178
+ self.exec_callback(:ack, method)
179
+ end
180
+
181
+
182
+ # Handler for Basic.Nack. By default, it just
183
+ # executes hook specified via the #confirm_failed method
184
+ # with a single argument, a protocol method class
185
+ # instance (an instance of AMQ::Protocol::Basic::Nack).
186
+ #
187
+ # @api plugin
188
+ def handle_basic_nack(method)
189
+ self.exec_callback(:nack, method)
190
+ end
191
+
192
+
193
+ def reset_state!
194
+ super
195
+
196
+ @confirmations = false
197
+ end
198
+
199
+
200
+ def self.included(host)
201
+ host.handle(Protocol::Confirm::SelectOk) do |client, frame|
202
+ method = frame.decode_payload
203
+ channel = client.connection.channels[frame.channel]
204
+ channel.handle_select_ok(method)
205
+ end
206
+
207
+ host.handle(Protocol::Basic::Ack) do |client, frame|
208
+ method = frame.decode_payload
209
+ channel = client.connection.channels[frame.channel]
210
+ channel.handle_basic_ack(method)
211
+ end
212
+
213
+ host.handle(Protocol::Basic::Nack) do |client, frame|
214
+ method = frame.decode_payload
215
+ channel = client.connection.channels[frame.channel]
216
+ channel.handle_basic_nack(method)
217
+ end
218
+ end # self.included(host)
219
+ end # ChannelMixin
220
+
221
+
222
+ module ExchangeMixin
223
+ # Publish message and then run #after_publish on channel belonging
224
+ # to the exchange. This is used for incrementing the publisher index.
225
+ #
226
+ # @api public
227
+ # @see AMQ::Client::Exchange#publish
228
+ # @see AMQ::Client::Extensions::RabbitMQ::Channel#publisher_index
229
+ # @return [self] self
230
+ def publish(*args)
231
+ super(*args)
232
+ @channel.after_publish(*args)
233
+
234
+ self
235
+ end # publish
236
+ end # ExchangeMixin
237
+ end # Confirm
238
+ end # RabbitMQ
239
+ end # Extensions
240
+
241
+
242
+ class Channel
243
+ # use modules, a native Ruby way of extension of existing classes,
244
+ # instead of reckless monkey-patching. MK.
245
+ include Extensions::RabbitMQ::Confirm::ChannelMixin
246
+ end # Channel
247
+
248
+ class Exchange
249
+ # use modules, a native Ruby way of extension of existing classes,
250
+ # instead of reckless monkey-patching. MK.
251
+ include Extensions::RabbitMQ::Confirm::ExchangeMixin
252
+ end # Exchange
253
+ end # Client
254
+ end # AMQ