reliable-msg 1.0.1 → 1.1.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.
- data/README +5 -1
- data/Rakefile +30 -23
- data/changelog.txt +32 -0
- data/lib/reliable-msg.rb +10 -5
- data/lib/reliable-msg/cli.rb +47 -3
- data/lib/reliable-msg/client.rb +213 -0
- data/lib/reliable-msg/message-store.rb +128 -49
- data/lib/reliable-msg/mysql.sql +7 -1
- data/lib/reliable-msg/queue-manager.rb +263 -58
- data/lib/reliable-msg/queue.rb +100 -253
- data/lib/reliable-msg/rails.rb +114 -0
- data/lib/reliable-msg/selector.rb +65 -75
- data/lib/reliable-msg/topic.rb +215 -0
- data/test/test-queue.rb +35 -5
- data/test/test-rails.rb +59 -0
- data/test/test-topic.rb +102 -0
- metadata +54 -41
- data/lib/uuid.rb +0 -384
- data/test/test-uuid.rb +0 -48
@@ -0,0 +1,114 @@
|
|
1
|
+
require "action_controller"
|
2
|
+
|
3
|
+
module ActionController #:nodoc:
|
4
|
+
|
5
|
+
class Base
|
6
|
+
|
7
|
+
# Convenience method for accessing queues from your Rails controller.
|
8
|
+
#
|
9
|
+
# Use this method in your controller class to create an attribute for accessing
|
10
|
+
# the named queue. The method can be called in one of three ways:
|
11
|
+
# * With a +Symbol+. Adds the named attribute to access a queue with the same name.
|
12
|
+
# * With a +String+. Adds the attribute <tt>queue</tt> to access the named queue.
|
13
|
+
# * With a +Symbol+ and a +String+. Adds the named attribute to access the named
|
14
|
+
# queue.
|
15
|
+
#
|
16
|
+
# For example
|
17
|
+
# queue 'default'
|
18
|
+
#
|
19
|
+
# def index
|
20
|
+
# queue.put "some message"
|
21
|
+
# render :text => "added message to queue 'default'"
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# :call-seq:
|
25
|
+
# queue symbol
|
26
|
+
# queue symbol, name
|
27
|
+
# queue name
|
28
|
+
#
|
29
|
+
def self.queue *args
|
30
|
+
raise ArgumentError, "Expecting a Symbol specifying the attribute name, a String specifying the queue name, or both" unless args.length > 0 && args.length <= 2
|
31
|
+
attr = name = nil
|
32
|
+
case args[0]
|
33
|
+
when String
|
34
|
+
raise ArgumentError, "When first argument is a String specifying the queue name, expecting to find only one argument" unless args.length == 1
|
35
|
+
attr = :queue
|
36
|
+
name = args[0]
|
37
|
+
when Symbol
|
38
|
+
attr = args[0]
|
39
|
+
if args.length == 1
|
40
|
+
name = args[0].to_s
|
41
|
+
else
|
42
|
+
raise ArgumetnError, "When first argument is a Symbol specifying the attribute name, expecting the second argument to be a String specifying the queue name or absent" unless args[1].instance_of?(String)
|
43
|
+
name = args[1]
|
44
|
+
end
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Expecting first argument to be a Symbol or a String"
|
47
|
+
end
|
48
|
+
quoted = "\"" << name.gsub("\"", "\\\"") << "\""
|
49
|
+
|
50
|
+
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
51
|
+
@@queue_#{attr.to_s} = ReliableMsg::Queue.new(#{quoted})
|
52
|
+
def #{attr.to_s}(*args)
|
53
|
+
raise ArgumentError, "Attribute #{attr} is accessed without any arguments, e.g. #{attr.to_s}.put(msg)" unless args.length == 0
|
54
|
+
@@queue_#{attr.to_s}
|
55
|
+
end
|
56
|
+
EOS
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Convenience method for accessing topics from your Rails controller.
|
61
|
+
#
|
62
|
+
# Use this method in your controller class to create an attribute for accessing
|
63
|
+
# the named topic. The method can be called in one of three ways:
|
64
|
+
# * With a +Symbol+. Adds the named attribute to access a topic with the same name.
|
65
|
+
# * With a +String+. Adds the attribute <tt>topic</tt> to access the named topic.
|
66
|
+
# * With a +Symbol+ and a +String+. Adds the named attribute to access the named
|
67
|
+
# topic.
|
68
|
+
#
|
69
|
+
# For example
|
70
|
+
# topic :notification
|
71
|
+
#
|
72
|
+
# def index
|
73
|
+
# :notification.put "something new"
|
74
|
+
# render :text => "added message to topic 'notification'"
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# :call-seq:
|
78
|
+
# topic symbol
|
79
|
+
# topic symbol, name
|
80
|
+
# topic name
|
81
|
+
#
|
82
|
+
def self.topic *args
|
83
|
+
raise ArgumentError, "Expecting a Symbol specifying the attribute name, a String specifying the topic name, or both" unless args.length > 0 && args.length <= 2
|
84
|
+
attr = name = nil
|
85
|
+
case args[0]
|
86
|
+
when String
|
87
|
+
raise ArgumentError, "When first argument is a String specifying the topic name, expecting to find only one argument" unless args.length == 1
|
88
|
+
attr = :topic
|
89
|
+
name = args[0]
|
90
|
+
when Symbol
|
91
|
+
attr = args[0]
|
92
|
+
if args.length == 1
|
93
|
+
name = args[0].to_s
|
94
|
+
else
|
95
|
+
raise ArgumetnError, "When first argument is a Symbol specifying the attribute name, expecting the second argument to be a String specifying the topic name or absent" unless args[1].instance_of?(String)
|
96
|
+
name = args[1]
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise ArgumentError, "Expecting first argument to be a Symbol or a String"
|
100
|
+
end
|
101
|
+
quoted = "\"" << name.gsub("\"", "\\\"") << "\""
|
102
|
+
|
103
|
+
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
104
|
+
@@topic_#{attr.to_s} = ReliableMsg::Topic.new(#{quoted})
|
105
|
+
def #{attr.to_s}(*args)
|
106
|
+
raise ArgumentError, "Attribute #{attr} is accessed without any arguments, e.g. #{attr.to_s}.put(msg)" unless args.length == 0
|
107
|
+
@@topic_#{attr.to_s}
|
108
|
+
end
|
109
|
+
EOS
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# = selector.rb - Deferred expression evaluation selector
|
3
3
|
#
|
4
4
|
# Author:: Assaf Arkin assaf@labnotes.org
|
5
|
-
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/
|
5
|
+
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
|
6
6
|
# Copyright:: Copyright (c) 2005 Assaf Arkin
|
7
7
|
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
8
8
|
#
|
@@ -10,98 +10,88 @@
|
|
10
10
|
# presentation on domain specific languages, and the BlankSlate source code.
|
11
11
|
#
|
12
12
|
#--
|
13
|
-
# Changes:
|
14
13
|
#++
|
15
14
|
|
16
15
|
module ReliableMsg #:nodoc:
|
17
16
|
|
17
|
+
# A message selector is used to retrieve specific messages from the queue
|
18
|
+
# by matching the message headers.
|
19
|
+
#
|
20
|
+
# The selector matches messages by calling the block for each potential
|
21
|
+
# message. The block can access (read-only) message headers by calling
|
22
|
+
# methods with the same name, or using <tt>[:symbol]</tt>. It returns true
|
23
|
+
# if a match is found.
|
24
|
+
#
|
25
|
+
# The following three examples are equivalent:
|
26
|
+
# selector = Queue::selector { priority > 2 }
|
27
|
+
# selector = queue.selector { priority > 2 }
|
28
|
+
# selector = Selector.new { [:priority] > 2 }
|
29
|
+
#
|
30
|
+
# The new function is always available and evaluates to the current time
|
31
|
+
# (in seconds from the Epoch).
|
32
|
+
#
|
33
|
+
# This example uses the delivery count and message creation date/time to
|
34
|
+
# implement a simple retry with back-out:
|
35
|
+
#
|
36
|
+
# MINUTE = 60
|
37
|
+
# HOUR = MINUTE * 60
|
38
|
+
# BACKOUT = [ 5 * MINUTE, HOUR, 4 * HOUR, 12 * HOUR ]
|
39
|
+
#
|
40
|
+
# selector = Queue::selector { delivered == 0 || BACKOUT[delivered - 1] + created <= now }
|
18
41
|
class Selector
|
19
42
|
|
20
|
-
|
21
|
-
# refuse to marshal the selector as an argument and attempt to create a remote
|
22
|
-
# object instead.
|
23
|
-
instance_methods.each { |name| undef_method name unless name =~ /^(__.*__)|respond_to\?|instance_eval$/ }
|
24
|
-
|
25
|
-
def initialize &block
|
26
|
-
# Call the block and hold the deferred value.
|
27
|
-
@value = self.instance_eval &block
|
28
|
-
end
|
29
|
-
|
30
|
-
def method_missing symbol, *args
|
31
|
-
if symbol == :__evaluate__
|
32
|
-
# Evaluate the selector with the headers passed in the argument.
|
33
|
-
@value.is_a?(Deferred) ? @value.__evaluate__(*args) : @value
|
34
|
-
else
|
35
|
-
# Create a deferred value for the missing method (a header).
|
36
|
-
raise ArgumentError, "Can't pass arguments to header" unless args.empty?
|
37
|
-
Header.new symbol
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
|
42
|
-
class Deferred #:nodoc:
|
43
|
-
|
44
|
-
# We're using DRb. Unless we support respond_to? and instance_eval?, DRb will
|
45
|
-
# refuse to marshal the selector as an argument and attempt to create a remote
|
46
|
-
# object instead.
|
47
|
-
instance_methods.each { |name| undef_method name unless name =~ /^(__.*__)|respond_to\?|instance_eval$/ }
|
48
|
-
|
49
|
-
def initialize target, operation, args
|
50
|
-
@target = target
|
51
|
-
@operation = operation
|
52
|
-
@args = args
|
53
|
-
end
|
54
|
-
|
55
|
-
def coerce value
|
56
|
-
[Constant.new(value), self]
|
57
|
-
end
|
58
|
-
|
59
|
-
def method_missing symbol, *args
|
60
|
-
if symbol == :__evaluate__
|
61
|
-
|
62
|
-
eval_args = @args.collect { |arg| arg.instance_of?(Deferred) ? arg.__evaluate__(*args) : arg }
|
63
|
-
@target.__evaluate__(*args).send @operation, *eval_args
|
64
|
-
else
|
65
|
-
Deferred.new self, symbol, args
|
66
|
-
end
|
67
|
-
end
|
43
|
+
ERROR_INVALID_SELECTOR_BLOCK = "Selector must be created with a block accepting no arguments" #:nodoc:
|
68
44
|
|
45
|
+
# Create a new selector that evaluates by calling the block.
|
46
|
+
#
|
47
|
+
# :call-seq:
|
48
|
+
# Selector.new { |headers| ... } -> selector
|
49
|
+
#
|
50
|
+
def initialize &block
|
51
|
+
raise ArgumentError, ERROR_INVALID_SELECTOR_BLOCK unless block && block.arity < 1
|
52
|
+
@block = block
|
69
53
|
end
|
70
54
|
|
71
|
-
class Header < Deferred #:nodoc:
|
72
55
|
|
73
|
-
|
74
|
-
|
75
|
-
|
56
|
+
# Matches the message headers with the selectors. Returns true
|
57
|
+
# if a match is made, false otherwise. May raise an error if
|
58
|
+
# there's an error in the expression.
|
59
|
+
#
|
60
|
+
# :call-seq:
|
61
|
+
# selector.match(headers) -> boolean
|
62
|
+
#
|
63
|
+
def match headers #:nodoc:
|
64
|
+
context = EvaluationContext.new headers
|
65
|
+
context.instance_eval(&@block)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
70
|
+
|
76
71
|
|
77
|
-
|
78
|
-
[Constant.new(value), self]
|
79
|
-
end
|
72
|
+
class EvaluationContext #:nodoc:
|
80
73
|
|
81
|
-
|
82
|
-
if symbol == :__evaluate__
|
83
|
-
args[0][@name]
|
84
|
-
else
|
85
|
-
Deferred.new self, symbol, args
|
86
|
-
end
|
87
|
-
end
|
74
|
+
instance_methods.each { |name| undef_method name unless name =~ /^(__.*__)|instance_eval$/ }
|
88
75
|
|
76
|
+
|
77
|
+
def initialize headers
|
78
|
+
@headers = headers
|
89
79
|
end
|
90
80
|
|
91
|
-
class Constant < Deferred #:nodoc:
|
92
81
|
|
93
|
-
|
94
|
-
|
95
|
-
|
82
|
+
def now
|
83
|
+
Time.now.to_i
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def [] symbol
|
88
|
+
@headers[symbol]
|
89
|
+
end
|
96
90
|
|
97
|
-
def method_missing symbol, *args
|
98
|
-
if symbol == :__evaluate__
|
99
|
-
@value
|
100
|
-
else
|
101
|
-
Deferred.new self, symbol, args
|
102
|
-
end
|
103
|
-
end
|
104
91
|
|
92
|
+
def method_missing symbol, *args, &block
|
93
|
+
raise ArgumentError, "Wrong number of arguments (#{args.length} for 0)" unless args.empty?
|
94
|
+
@headers[symbol]
|
105
95
|
end
|
106
96
|
|
107
97
|
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
#
|
2
|
+
# = topic.rb - Publish to topic API
|
3
|
+
#
|
4
|
+
# Author:: Assaf Arkin assaf@labnotes.org
|
5
|
+
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
|
6
|
+
# Copyright:: Copyright (c) 2005 Assaf Arkin
|
7
|
+
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
8
|
+
#
|
9
|
+
#--
|
10
|
+
#++
|
11
|
+
|
12
|
+
require 'drb'
|
13
|
+
require 'reliable-msg/client'
|
14
|
+
require 'reliable-msg/selector'
|
15
|
+
|
16
|
+
|
17
|
+
module ReliableMsg
|
18
|
+
|
19
|
+
# == Pub/Sub Topic API
|
20
|
+
#
|
21
|
+
# Use the Topic object to publish a message on a topic, get messages from topics.
|
22
|
+
#
|
23
|
+
# You can create a Topic object that connects to a single topic by passing the
|
24
|
+
# topic name to the initialized. You can also access other topics by specifying
|
25
|
+
# the destination topic when putting a message.
|
26
|
+
#
|
27
|
+
# For example:
|
28
|
+
# topic = Topic.new 'my-topic'
|
29
|
+
# # Publish a message on the topic, expiring in 30 seconds.
|
30
|
+
# msg = 'lorem ipsum'
|
31
|
+
# mid = topic.put msg, :expires=>30
|
32
|
+
# # Retrieve and process a message on the topic.
|
33
|
+
# topic.get do |msg|
|
34
|
+
# if msg.id == mid
|
35
|
+
# print "Retrieved same message"
|
36
|
+
# end
|
37
|
+
# print "Message text: #{msg.object}"
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# See Topic.get and Topic.put for more examples.
|
41
|
+
class Topic < Client
|
42
|
+
|
43
|
+
INIT_OPTIONS = [:expires, :drb_uri, :tx_timeout, :connect_count]
|
44
|
+
|
45
|
+
# The optional argument +topic+ specifies the topic name. The application can
|
46
|
+
# still publish messages on other topics by specifying the destination topics
|
47
|
+
# name in the header.
|
48
|
+
#
|
49
|
+
# The following options can be passed to the initializer:
|
50
|
+
# * <tt>:expires</tt> -- Message expiration in seconds. Default for new messages.
|
51
|
+
# * <tt>:drb_uri</tt> -- DRb URI for connecting to the queue manager. Only
|
52
|
+
# required when using a remote queue manager, or different port.
|
53
|
+
# * <tt>:tx_timeout</tt> -- Transaction timeout. See tx_timeout.
|
54
|
+
# * <tt>:connect_count</tt> -- Connection attempts. See connect_count.
|
55
|
+
#
|
56
|
+
# :call-seq:
|
57
|
+
# Topic.new([name [,options]]) -> topic
|
58
|
+
#
|
59
|
+
def initialize topic = nil, options = nil
|
60
|
+
options.each do |name, value|
|
61
|
+
raise RuntimeError, format(ERROR_INVALID_OPTION, name) unless INIT_OPTIONS.include?(name)
|
62
|
+
instance_variable_set "@#{name.to_s}".to_sym, value
|
63
|
+
end if options
|
64
|
+
@topic = topic
|
65
|
+
@seen = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Publish a message on the topic.
|
70
|
+
#
|
71
|
+
# The +message+ argument is required, but may be +nil+
|
72
|
+
#
|
73
|
+
# Headers are optional. Headers are used to provide the application with additional
|
74
|
+
# information about the message, and can be used to retrieve messages (see Topic.get
|
75
|
+
# for discussion of selectors). Some headers are used to handle message processing
|
76
|
+
# internally (e.g. <tt>:expires</tt>).
|
77
|
+
#
|
78
|
+
# Each header uses a symbol for its name. The value may be string, numeric, true/false
|
79
|
+
# or nil. No other objects are allowed. To improve performance, keep headers as small
|
80
|
+
# as possible.
|
81
|
+
#
|
82
|
+
# The following headers have special meaning:
|
83
|
+
# * <tt>:topic</tt> -- Publish the onn the named topic. Otherwise, uses the topic
|
84
|
+
# specified when creating this Topic object.
|
85
|
+
# * <tt>:expires</tt> -- Message expiration in seconds. Messages do not expire unless
|
86
|
+
# specified. Zero or +nil+ means no expiration.
|
87
|
+
# * <tt>:expires_at</tt> -- Specifies when the message expires (timestamp). Alternative
|
88
|
+
# to <tt>:expires</tt>.
|
89
|
+
#
|
90
|
+
# Headers can be set on a per-topic basis when the Topic is created. This only affects
|
91
|
+
# messages put through that Topic object.
|
92
|
+
#
|
93
|
+
# For example:
|
94
|
+
# topic.put updates
|
95
|
+
# topic.put notice, :expires=>10
|
96
|
+
# topic.put object, :topic=>'other-topic'
|
97
|
+
#
|
98
|
+
# :call-seq:
|
99
|
+
# topic.put(message[, headers])
|
100
|
+
#
|
101
|
+
def put message, headers = nil
|
102
|
+
tx = Thread.current[THREAD_CURRENT_TX]
|
103
|
+
# Use headers supplied by callers, or defaults for this topic.
|
104
|
+
defaults = {
|
105
|
+
:expires=> @expires
|
106
|
+
}
|
107
|
+
headers = headers ? defaults.merge(headers) : defaults
|
108
|
+
# Serialize the message before sending to queue manager. We need the
|
109
|
+
# message to be serialized for storage, this just saves duplicate
|
110
|
+
# serialization when using DRb.
|
111
|
+
message = Marshal::dump message
|
112
|
+
# If inside a transaction, always send to the same queue manager, otherwise,
|
113
|
+
# allow repeated() to try and access multiple queue managers.
|
114
|
+
if tx
|
115
|
+
tx[:qm].publish(:message=>message, :headers=>headers, :topic=>(headers[:topic] || @topic), :tid=>tx[:tid])
|
116
|
+
else
|
117
|
+
repeated { |qm| qm.publish :message=>message, :headers=>headers, :topic=>(headers[:topic] || @topic) }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# Get a message on the topic.
|
123
|
+
#
|
124
|
+
# Call with no arguments to retrieve the last message published on the topic. Call with
|
125
|
+
# selectors to retrieve only matching messages. See also Queue.get.
|
126
|
+
#
|
127
|
+
# The following headers have special meaning:
|
128
|
+
# * <tt>:id</tt> -- The message identifier.
|
129
|
+
# * <tt>:created</tt> -- Indicates timestamp (in seconds) when the message was created.
|
130
|
+
# * <tt>:expires_at</tt> -- Indicates timestamp (in seconds) when the message will expire,
|
131
|
+
# +nil+ if the message does not expire.
|
132
|
+
#
|
133
|
+
# Call this method without a block to return the message. The returned object is of type
|
134
|
+
# Message, or +nil+ if no message is found.
|
135
|
+
#
|
136
|
+
# Call this method in a block to retrieve and process the message. The block is called with
|
137
|
+
# the Message object, returning the result of the block. Returns +nil+ if no message is found.
|
138
|
+
#
|
139
|
+
# All operations performed on the topic inside the block are part of the same transaction.
|
140
|
+
# See Queue.get for discussion about transactions. Note that retry counts and delivery modes
|
141
|
+
# do not apply to Topics. A message remains published on the topic until replaced by another
|
142
|
+
# message.
|
143
|
+
#
|
144
|
+
# :call-seq:
|
145
|
+
# topic.get([selector]) -> msg or nil
|
146
|
+
# topic.get([selector]) {|msg| ... } -> obj
|
147
|
+
#
|
148
|
+
# See: Message
|
149
|
+
#
|
150
|
+
def get selector = nil, &block
|
151
|
+
tx = old_tx = Thread.current[THREAD_CURRENT_TX]
|
152
|
+
# If block, begin a new transaction.
|
153
|
+
if block
|
154
|
+
tx = {:qm=>qm}
|
155
|
+
tx[:tid] = tx[:qm].begin tx_timeout
|
156
|
+
Thread.current[THREAD_CURRENT_TX] = tx
|
157
|
+
end
|
158
|
+
result = begin
|
159
|
+
# Validate the selector: nil or hash.
|
160
|
+
selector = case selector
|
161
|
+
when Hash, Selector, nil
|
162
|
+
selector
|
163
|
+
else
|
164
|
+
raise ArgumentError, ERROR_INVALID_SELECTOR
|
165
|
+
end
|
166
|
+
# If inside a transaction, always retrieve from the same queue manager,
|
167
|
+
# otherwise, allow repeated() to try and access multiple queue managers.
|
168
|
+
message = if tx
|
169
|
+
tx[:qm].retrieve :seen=>@seen, :topic=>@topic, :selector=>(selector.is_a?(Selector) ? nil : selector), :tid=>tx[:tid]
|
170
|
+
else
|
171
|
+
repeated { |qm| qm.retrieve :seen=>@seen, :topic=>@topic, :selector=>(selector.is_a?(Selector) ? nil : selector) }
|
172
|
+
end
|
173
|
+
# Result is either message, or result from processing block. Note that
|
174
|
+
# calling block may raise an exception. We deserialize the message here
|
175
|
+
# for two reasons:
|
176
|
+
# 1. It creates a distinct copy, so changing the message object and returning
|
177
|
+
# it to the queue (abort) does not affect other consumers.
|
178
|
+
# 2. The message may rely on classes known to the client but not available
|
179
|
+
# to the queue manager.
|
180
|
+
result = if message
|
181
|
+
# Do not process message unless selector matches. Do not mark
|
182
|
+
# message as seen either, since we may retrieve it if the selector
|
183
|
+
# changes.
|
184
|
+
if selector.is_a?(Selector)
|
185
|
+
return nil unless selector.match message[:headers]
|
186
|
+
end
|
187
|
+
@seen = message[:id]
|
188
|
+
message = Message.new(message[:id], message[:headers], Marshal::load(message[:message]))
|
189
|
+
block ? block.call(message) : message
|
190
|
+
end
|
191
|
+
rescue Exception=>error
|
192
|
+
# Abort the transaction if we started it. Propagate error.
|
193
|
+
qm.abort(tx[:tid]) if block
|
194
|
+
raise error
|
195
|
+
ensure
|
196
|
+
# Resume the old transaction.
|
197
|
+
Thread.current[THREAD_CURRENT_TX] = old_tx if block
|
198
|
+
end
|
199
|
+
# Commit the transaction and return the result. We do this outside the main
|
200
|
+
# block, since we don't abort in case of error (commit is one-phase) and we
|
201
|
+
# don't retain the transaction association, it completes by definition.
|
202
|
+
qm.commit(tx[:tid]) if block
|
203
|
+
result
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
# Returns the topic name.
|
208
|
+
def name
|
209
|
+
@topic
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|