reliable-msg 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|