amq-client 0.5.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 (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