transaction-simple 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +13 -2
- data/Install +5 -1
- data/Rakefile +11 -17
- data/Readme +186 -103
- data/lib/transaction/simple.rb +295 -193
- data/lib/transaction/simple/group.rb +133 -0
- data/lib/transaction/simple/threadsafe.rb +52 -0
- data/lib/transaction/simple/threadsafe/group.rb +23 -0
- data/tests/{tests.rb → tc_transaction_simple.rb} +6 -131
- data/tests/tc_transaction_simple_group.rb +44 -0
- data/tests/tc_transaction_simple_threadsafe.rb +135 -0
- data/tests/testall.rb +20 -0
- metadata +24 -13
- data/ChangeLog +0 -20
- data/README +0 -110
data/Changelog
CHANGED
@@ -1,4 +1,15 @@
|
|
1
|
-
$Id: Changelog,v 1.
|
1
|
+
$Id: Changelog,v 1.5 2005/05/05 16:16:49 austin Exp $
|
2
|
+
|
3
|
+
== Transaction::simple 1.3.0
|
4
|
+
* Updated to fix a lot of warnings.
|
5
|
+
* Added a per-transaction-object list of excluded instance variables.
|
6
|
+
* Moved Transaction::simple::ThreadSafe to transaction/simple/threadsafe.
|
7
|
+
* Added transaction groups. Transaction groups are wrapper objects to allow
|
8
|
+
the coordination of transactions with a group of objects. There are both
|
9
|
+
normal and threadsafe versions of transaction groups.
|
10
|
+
* Fixed a long-standing problem where instance variables that were added to an
|
11
|
+
object after a transaction was started would remain.
|
12
|
+
* Reorganised unit tests.
|
2
13
|
|
3
14
|
== Transaction::Simple 1.2.0
|
4
15
|
* Added a RubyGem.
|
@@ -11,7 +22,7 @@ $Id: Changelog,v 1.2 2004/09/14 18:46:15 austin Exp $
|
|
11
22
|
* Added Transaction::Simple::ThreadSafe for truly atomic and thread-safe
|
12
23
|
transactions.
|
13
24
|
* Fixed the description of Transaction::Simple to note that it is *not* atomic
|
14
|
-
because it is not
|
25
|
+
because it is not implicitly thread-safe.
|
15
26
|
* Added support for named transactions. Named transactions can be used to make
|
16
27
|
checkpoints that can be committed, aborted, or rewound without explicitly
|
17
28
|
committing, aborting, or rewinding the intervening transactions.
|
data/Install
CHANGED
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ require 'transaction/simple'
|
|
8
8
|
require 'archive/tar/minitar'
|
9
9
|
require 'zlib'
|
10
10
|
|
11
|
-
DISTDIR = "transaction-simple-#{Transaction::Simple::
|
11
|
+
DISTDIR = "transaction-simple-#{Transaction::Simple::TRANSACTION_SIMPLE_VERSION}"
|
12
12
|
TARDIST = "../#{DISTDIR}.tar.gz"
|
13
13
|
|
14
14
|
DATE_RE = %r<(\d{4})[./-]?(\d{2})[./-]?(\d{2})(?:[\sT]?(\d{2})[:.]?(\d{2})[:.]?(\d{2})?)?>
|
@@ -34,7 +34,7 @@ task :test do |t|
|
|
34
34
|
|
35
35
|
$LOAD_PATH.unshift('tests')
|
36
36
|
$stderr.puts "Checking for test cases:" if t.verbose
|
37
|
-
Dir['tests
|
37
|
+
Dir['tests/**/tc_*.rb'].each do |testcase|
|
38
38
|
$stderr.puts "\t#{testcase}" if t.verbose
|
39
39
|
load testcase
|
40
40
|
end
|
@@ -49,7 +49,7 @@ task :test do |t|
|
|
49
49
|
end
|
50
50
|
|
51
51
|
spec = eval(File.read("transaction-simple.gemspec"))
|
52
|
-
spec.version = Transaction::Simple::
|
52
|
+
spec.version = Transaction::Simple::TRANSACTION_SIMPLE_VERSION
|
53
53
|
desc "Build the RubyGem for Transaction::Simple"
|
54
54
|
task :gem => [ :test ]
|
55
55
|
Rake::GemPackageTask.new(spec) do |g|
|
@@ -101,20 +101,14 @@ file TARDIST => [ :test ] do |t|
|
|
101
101
|
end
|
102
102
|
task TARDIST => [ :test ]
|
103
103
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
task :signtar => [ :tar ] do
|
113
|
-
sign TARDIST
|
114
|
-
end
|
115
|
-
task :signgem => [ :gem ] do
|
116
|
-
sign "../#{DISTDIR}.gem"
|
104
|
+
desc "Build the rdoc documentation for Transaction::Simple"
|
105
|
+
task :docs do
|
106
|
+
require 'rdoc/rdoc'
|
107
|
+
rdoc_options = %w(--title Transaction::Simple --main Readme --line-numbers)
|
108
|
+
files = FileList[*%w(Readme Changelog bin/**/*.rb lib/**/*.rb)]
|
109
|
+
rdoc_options += files.to_a
|
110
|
+
RDoc::RDoc.new.document(rdoc_options)
|
117
111
|
end
|
118
112
|
|
119
113
|
desc "Build everything."
|
120
|
-
task :default => [ :
|
114
|
+
task :default => [ :tar, :gem ]
|
data/Readme
CHANGED
@@ -1,110 +1,193 @@
|
|
1
|
-
Transaction::Simple for Ruby
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
1
|
+
= Transaction::Simple for Ruby
|
2
|
+
Transaction::Simple provides a generic way to add active transaction
|
3
|
+
support to objects. The transaction methods added by this module will
|
4
|
+
work with most objects, excluding those that cannot be Marshal-ed
|
5
|
+
(bindings, procedure objects, IO instances, or singleton objects).
|
6
|
+
|
7
|
+
The transactions supported by Transaction::Simple are not backend
|
8
|
+
transaction; that is, they are not associated with any sort of data
|
9
|
+
store. They are "live" transactions occurring in memory and in the
|
10
|
+
object itself. This is to allow "test" changes to be made to an object
|
11
|
+
before making the changes permanent.
|
12
|
+
|
13
|
+
Transaction::Simple can handle an "infinite" number of transaction
|
14
|
+
levels (limited only by memory). If I open two transactions, commit the
|
15
|
+
second, but abort the first, the object will revert to the original
|
16
|
+
version.
|
17
|
+
|
18
|
+
Transaction::Simple supports "named" transactions, so that multiple
|
19
|
+
levels of transactions can be committed, aborted, or rewound by
|
20
|
+
referring to the appropriate name of the transaction. Names may be any
|
21
|
+
object except nil.
|
22
|
+
|
23
|
+
Version 1.3.0 of Transaction::Simple adds transaction groups. A
|
24
|
+
transaction group is an object wrapper that manages a group of objects
|
25
|
+
as if they were a single object for the purpose of transaction
|
26
|
+
management. All transactions for this group of objects should be
|
27
|
+
performed against the transaction group object, not against individual
|
28
|
+
objects in the group.
|
29
|
+
|
30
|
+
Copyright: Copyright � 2003 - 2005 by Austin Ziegler
|
31
|
+
Version: 1.3.0
|
26
32
|
Licence: MIT-Style
|
27
33
|
|
28
|
-
Thanks to David Black and Mauricio Fern�ndez for their help with this
|
34
|
+
Thanks to David Black and Mauricio Fern�ndez for their help with this
|
35
|
+
library.
|
29
36
|
|
30
|
-
Usage
|
31
|
-
-----
|
37
|
+
== Usage
|
32
38
|
include 'transaction/simple'
|
33
39
|
|
34
|
-
v = "Hello, you." #
|
35
|
-
v.extend(Transaction::Simple) #
|
36
|
-
|
37
|
-
v.start_transaction #
|
38
|
-
v.transaction_open? #
|
39
|
-
v.gsub!(/you/, "world") #
|
40
|
-
|
41
|
-
v.rewind_transaction #
|
42
|
-
v.transaction_open? #
|
43
|
-
|
44
|
-
v.gsub!(/you/, "HAL") #
|
45
|
-
v.abort_transaction #
|
46
|
-
v.transaction_open? #
|
47
|
-
|
48
|
-
v.start_transaction #
|
49
|
-
v.start_transaction #
|
50
|
-
|
51
|
-
v.transaction_open? #
|
52
|
-
v.gsub!(/you/, "HAL") #
|
53
|
-
|
54
|
-
v.commit_transaction #
|
55
|
-
v.transaction_open? #
|
56
|
-
v.abort_transaction #
|
57
|
-
v.transaction_open? #
|
58
|
-
|
59
|
-
Named Transaction Usage
|
60
|
-
|
61
|
-
v
|
62
|
-
|
63
|
-
|
64
|
-
v.
|
65
|
-
v.transaction_open?
|
66
|
-
v.transaction_open?(:
|
67
|
-
v.
|
68
|
-
|
69
|
-
|
70
|
-
v.
|
71
|
-
v.
|
72
|
-
v.
|
73
|
-
v.transaction_open?
|
74
|
-
v.transaction_open?(:
|
75
|
-
|
76
|
-
|
77
|
-
v.
|
78
|
-
v.
|
79
|
-
v.
|
80
|
-
v.
|
81
|
-
v.
|
82
|
-
|
83
|
-
|
84
|
-
v.
|
85
|
-
v.
|
86
|
-
v.
|
87
|
-
|
88
|
-
|
89
|
-
v.
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
40
|
+
v = "Hello, you." # -> "Hello, you."
|
41
|
+
v.extend(Transaction::Simple) # -> "Hello, you."
|
42
|
+
|
43
|
+
v.start_transaction # -> ... (a Marshal string)
|
44
|
+
v.transaction_open? # -> true
|
45
|
+
v.gsub!(/you/, "world") # -> "Hello, world."
|
46
|
+
|
47
|
+
v.rewind_transaction # -> "Hello, you."
|
48
|
+
v.transaction_open? # -> true
|
49
|
+
|
50
|
+
v.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
51
|
+
v.abort_transaction # -> "Hello, you."
|
52
|
+
v.transaction_open? # -> false
|
53
|
+
|
54
|
+
v.start_transaction # -> ... (a Marshal string)
|
55
|
+
v.start_transaction # -> ... (a Marshal string)
|
56
|
+
|
57
|
+
v.transaction_open? # -> true
|
58
|
+
v.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
59
|
+
|
60
|
+
v.commit_transaction # -> "Hello, HAL."
|
61
|
+
v.transaction_open? # -> true
|
62
|
+
v.abort_transaction # -> "Hello, you."
|
63
|
+
v.transaction_open? # -> false
|
64
|
+
|
65
|
+
== Named Transaction Usage
|
66
|
+
v = "Hello, you." # -> "Hello, you."
|
67
|
+
v.extend(Transaction::Simple) # -> "Hello, you."
|
68
|
+
|
69
|
+
v.start_transaction(:first) # -> ... (a Marshal string)
|
70
|
+
v.transaction_open? # -> true
|
71
|
+
v.transaction_open?(:first) # -> true
|
72
|
+
v.transaction_open?(:second) # -> false
|
73
|
+
v.gsub!(/you/, "world") # -> "Hello, world."
|
74
|
+
|
75
|
+
v.start_transaction(:second) # -> ... (a Marshal string)
|
76
|
+
v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
77
|
+
v.rewind_transaction(:first) # -> "Hello, you."
|
78
|
+
v.transaction_open? # -> true
|
79
|
+
v.transaction_open?(:first) # -> true
|
80
|
+
v.transaction_open?(:second) # -> false
|
81
|
+
|
82
|
+
v.gsub!(/you/, "world") # -> "Hello, world."
|
83
|
+
v.start_transaction(:second) # -> ... (a Marshal string)
|
84
|
+
v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
85
|
+
v.transaction_name # -> :second
|
86
|
+
v.abort_transaction(:first) # -> "Hello, you."
|
87
|
+
v.transaction_open? # -> false
|
88
|
+
|
89
|
+
v.start_transaction(:first) # -> ... (a Marshal string)
|
90
|
+
v.gsub!(/you/, "world") # -> "Hello, world."
|
91
|
+
v.start_transaction(:second) # -> ... (a Marshal string)
|
92
|
+
v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
93
|
+
|
94
|
+
v.commit_transaction(:first) # -> "Hello, HAL."
|
95
|
+
v.transaction_open? # -> false
|
96
|
+
|
97
|
+
== Block Transaction Usage
|
98
|
+
v = "Hello, you." # -> "Hello, you."
|
99
|
+
Transaction::Simple.start(v) do |tv|
|
100
|
+
# v has been extended with Transaction::Simple and an unnamed
|
101
|
+
# transaction has been started.
|
102
|
+
tv.transaction_open? # -> true
|
103
|
+
tv.gsub!(/you/, "world") # -> "Hello, world."
|
104
|
+
|
105
|
+
tv.rewind_transaction # -> "Hello, you."
|
106
|
+
tv.transaction_open? # -> true
|
107
|
+
|
108
|
+
tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
109
|
+
# The following breaks out of the transaction block after
|
110
|
+
# aborting the transaction.
|
111
|
+
tv.abort_transaction # -> "Hello, you."
|
112
|
+
end
|
113
|
+
# v still has Transaction::Simple applied from here on out.
|
114
|
+
v.transaction_open? # -> false
|
115
|
+
|
116
|
+
Transaction::Simple.start(v) do |tv|
|
117
|
+
tv.start_transaction # -> ... (a Marshal string)
|
118
|
+
|
119
|
+
tv.transaction_open? # -> true
|
120
|
+
tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
121
|
+
|
122
|
+
# If #commit_transaction were called without having started a
|
123
|
+
# second transaction, then it would break out of the transaction
|
124
|
+
# block after committing the transaction.
|
125
|
+
tv.commit_transaction # -> "Hello, HAL."
|
126
|
+
tv.transaction_open? # -> true
|
127
|
+
tv.abort_transaction # -> "Hello, you."
|
128
|
+
end
|
129
|
+
v.transaction_open? # -> false
|
130
|
+
|
131
|
+
== Transaction Groups
|
132
|
+
require 'transaction/simple/group'
|
133
|
+
|
134
|
+
x = "Hello, you."
|
135
|
+
y = "And you, too."
|
136
|
+
|
137
|
+
g = Transaction::Simple::Group.new(x, y)
|
138
|
+
g.start_transaction(:first) # -> [ x, y ]
|
139
|
+
g.transaction_open?(:first) # -> true
|
140
|
+
x.transaction_open?(:first) # -> true
|
141
|
+
y.transaction_open?(:first) # -> true
|
142
|
+
|
143
|
+
x.gsub!(/you/, "world") # -> "Hello, world."
|
144
|
+
y.gsub!(/you/, "me") # -> "And me, too."
|
145
|
+
|
146
|
+
g.start_transaction(:second) # -> [ x, y ]
|
147
|
+
x.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
148
|
+
y.gsub!(/me/, "Dave") # -> "And Dave, too."
|
149
|
+
|
150
|
+
g.rewind_transaction(:second) # -> [ x, y ]
|
151
|
+
x # -> "Hello, world."
|
152
|
+
y # -> "And me, too."
|
153
|
+
|
154
|
+
x.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
155
|
+
y.gsub!(/me/, "Dave") # -> "And Dave, too."
|
156
|
+
|
157
|
+
g.commit_transaction(:second) # -> [ x, y ]
|
158
|
+
x # -> "Hello, HAL."
|
159
|
+
y # -> "And Dave, too."
|
160
|
+
|
161
|
+
g.abort_transaction(:first) # -> [ x, y ]
|
162
|
+
x = -> "Hello, you."
|
163
|
+
y = -> "And you, too."
|
164
|
+
|
165
|
+
== Thread Safety
|
166
|
+
Threadsafe version of Transaction::Simple and Transaction::Simple::Group
|
167
|
+
exist; these are loaded from 'transaction/simple/threadsafe' and
|
168
|
+
'transaction/simple/threadsafe/group', respectively, and are represented
|
169
|
+
in Ruby code as Transaction::Simple::ThreadSafe and
|
170
|
+
Transaction::Simple::ThreadSafe::Group, respectively.
|
171
|
+
|
172
|
+
== Contraindications
|
173
|
+
While Transaction::Simple is very useful, it has some severe limitations
|
174
|
+
that must be understood. Transaction::Simple:
|
96
175
|
|
97
176
|
* uses Marshal. Thus, any object which cannot be Marshal-ed cannot use
|
98
|
-
Transaction::Simple.
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
"
|
108
|
-
|
109
|
-
|
110
|
-
|
177
|
+
Transaction::Simple. In my experience, this affects singleton objects
|
178
|
+
more often than any other object. It may be that Ruby 2.0 will solve
|
179
|
+
this problem.
|
180
|
+
* does not manage resources. Resources external to the object and its
|
181
|
+
instance variables are not managed at all. However, all instance
|
182
|
+
variables and objects "belonging" to those instance variables are
|
183
|
+
managed. If there are object reference counts to be handled,
|
184
|
+
Transaction::Simple will probably cause problems.
|
185
|
+
* is not thread-safe. In the ACID ("atomic, consistent, isolated,
|
186
|
+
durable") test, Transaction::Simple provides C and D, but it is up to
|
187
|
+
the user of Transaction::Simple to provide isolation. Transactions
|
188
|
+
should be considered "critical sections" in multi-threaded
|
189
|
+
applications. Thread safety can be ensured with
|
190
|
+
Transaction::Simple::ThreadSafe. With transaction groups, some level
|
191
|
+
of atomicity is assured.
|
192
|
+
* does not maintain Object#__id__ values on rewind or abort. This may
|
193
|
+
change for future versions.
|
data/lib/transaction/simple.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# :title: Transaction::Simple
|
1
|
+
# :title: Transaction::Simple -- Active Object Transaction Support for Ruby
|
2
2
|
# :main: Transaction::Simple
|
3
3
|
#
|
4
4
|
# == Licence
|
@@ -23,55 +23,86 @@
|
|
23
23
|
#--
|
24
24
|
# Transaction::Simple
|
25
25
|
# Simple object transaction support for Ruby
|
26
|
-
# Version 1.
|
26
|
+
# Version 1.3.0
|
27
27
|
#
|
28
|
-
# Copyright (c) 2003 -
|
28
|
+
# Copyright (c) 2003 - 2005 Austin Ziegler
|
29
29
|
#
|
30
|
-
# $Id: simple.rb,v 1.
|
30
|
+
# $Id: simple.rb,v 1.5 2005/05/05 16:16:49 austin Exp $
|
31
31
|
#++
|
32
|
-
#
|
33
|
-
require 'thread'
|
34
|
-
|
35
|
-
# The "Transaction" namespace can be used for additional transactional
|
32
|
+
# The "Transaction" namespace can be used for additional transaction
|
36
33
|
# support objects and modules.
|
37
34
|
module Transaction
|
38
|
-
# A standard exception for
|
35
|
+
# A standard exception for transaction errors.
|
39
36
|
class TransactionError < StandardError; end
|
37
|
+
# The TransactionAborted exception is used to indicate when a
|
38
|
+
# transaction has been aborted in the block form.
|
40
39
|
class TransactionAborted < Exception; end
|
40
|
+
# The TransactionCommitted exception is used to indicate when a
|
41
|
+
# transaction has been committed in the block form.
|
41
42
|
class TransactionCommitted < Exception; end
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
|
44
|
+
te = "Transaction Error: %s"
|
45
|
+
|
46
|
+
Messages = {
|
47
|
+
:bad_debug_object =>
|
48
|
+
te % "the transaction debug object must respond to #<<.",
|
49
|
+
:unique_names =>
|
50
|
+
te % "named transactions must be unique.",
|
51
|
+
:no_transaction_open =>
|
52
|
+
te % "no transaction open.",
|
53
|
+
:cannot_rewind_no_transaction =>
|
54
|
+
te % "cannot rewind; there is no current transaction.",
|
55
|
+
:cannot_rewind_named_transaction =>
|
56
|
+
te % "cannot rewind to transaction %s because it does not exist.",
|
57
|
+
:cannot_rewind_transaction_before_block =>
|
58
|
+
te % "cannot rewind a transaction started before the execution block.",
|
59
|
+
:cannot_abort_no_transaction =>
|
60
|
+
te % "cannot abort; there is no current transaction.",
|
61
|
+
:cannot_abort_transaction_before_block =>
|
62
|
+
te % "cannot abort a transaction started before the execution block.",
|
63
|
+
:cannot_abort_named_transaction =>
|
64
|
+
te % "cannot abort nonexistant transaction %s.",
|
65
|
+
:cannot_commit_no_transaction =>
|
66
|
+
te % "cannot commit; there is no current transaction.",
|
67
|
+
:cannot_commit_transaction_before_block =>
|
68
|
+
te % "cannot commit a transaction started before the execution block.",
|
69
|
+
:cannot_commit_named_transaction =>
|
70
|
+
te % "cannot commit nonexistant transaction %s.",
|
71
|
+
:cannot_start_empty_block_transaction =>
|
72
|
+
te % "cannot start a block transaction with no objects.",
|
73
|
+
:cannot_obtain_transaction_lock =>
|
74
|
+
te % "cannot obtain transaction lock for #%s.",
|
75
|
+
}
|
45
76
|
|
46
77
|
# = Transaction::Simple for Ruby
|
47
78
|
# Simple object transaction support for Ruby
|
48
79
|
#
|
49
80
|
# == Introduction
|
50
|
-
#
|
51
|
-
# Transaction::Simple provides a generic way to add active transactional
|
81
|
+
# Transaction::Simple provides a generic way to add active transaction
|
52
82
|
# support to objects. The transaction methods added by this module will
|
53
83
|
# work with most objects, excluding those that cannot be
|
54
84
|
# <i>Marshal</i>ed (bindings, procedure objects, IO instances, or
|
55
85
|
# singleton objects).
|
56
86
|
#
|
57
87
|
# The transactions supported by Transaction::Simple are not backed
|
58
|
-
# transactions;
|
59
|
-
#
|
60
|
-
#
|
88
|
+
# transactions; they are not associated with any sort of data store.
|
89
|
+
# They are "live" transactions occurring in memory and in the object
|
90
|
+
# itself. This is to allow "test" changes to be made to an object
|
61
91
|
# before making the changes permanent.
|
62
92
|
#
|
63
|
-
# Transaction::Simple can handle an "infinite" number of
|
93
|
+
# Transaction::Simple can handle an "infinite" number of transaction
|
64
94
|
# levels (limited only by memory). If I open two transactions, commit
|
65
|
-
# the
|
95
|
+
# the second, but abort the first, the object will revert to the
|
66
96
|
# original version.
|
67
97
|
#
|
68
98
|
# Transaction::Simple supports "named" transactions, so that multiple
|
69
99
|
# levels of transactions can be committed, aborted, or rewound by
|
70
100
|
# referring to the appropriate name of the transaction. Names may be any
|
71
|
-
# object *except* +nil+.
|
101
|
+
# object *except* +nil+. As with Hash keys, String names will be
|
102
|
+
# duplicated and frozen before using.
|
72
103
|
#
|
73
|
-
# Copyright:: Copyright � 2003 -
|
74
|
-
# Version:: 1.
|
104
|
+
# Copyright:: Copyright � 2003 - 2005 by Austin Ziegler
|
105
|
+
# Version:: 1.3.0
|
75
106
|
# Licence:: MIT-Style
|
76
107
|
#
|
77
108
|
# Thanks to David Black for help with the initial concept that led to
|
@@ -80,138 +111,145 @@ module Transaction
|
|
80
111
|
# == Usage
|
81
112
|
# include 'transaction/simple'
|
82
113
|
#
|
83
|
-
# v = "Hello, you." #
|
84
|
-
# v.extend(Transaction::Simple) #
|
114
|
+
# v = "Hello, you." # -> "Hello, you."
|
115
|
+
# v.extend(Transaction::Simple) # -> "Hello, you."
|
85
116
|
#
|
86
|
-
# v.start_transaction #
|
87
|
-
# v.transaction_open? #
|
88
|
-
# v.gsub!(/you/, "world") #
|
117
|
+
# v.start_transaction # -> ... (a Marshal string)
|
118
|
+
# v.transaction_open? # -> true
|
119
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
89
120
|
#
|
90
|
-
# v.rewind_transaction #
|
91
|
-
# v.transaction_open? #
|
121
|
+
# v.rewind_transaction # -> "Hello, you."
|
122
|
+
# v.transaction_open? # -> true
|
92
123
|
#
|
93
|
-
# v.gsub!(/you/, "HAL") #
|
94
|
-
# v.abort_transaction #
|
95
|
-
# v.transaction_open? #
|
124
|
+
# v.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
125
|
+
# v.abort_transaction # -> "Hello, you."
|
126
|
+
# v.transaction_open? # -> false
|
96
127
|
#
|
97
|
-
# v.start_transaction #
|
98
|
-
# v.start_transaction #
|
128
|
+
# v.start_transaction # -> ... (a Marshal string)
|
129
|
+
# v.start_transaction # -> ... (a Marshal string)
|
99
130
|
#
|
100
|
-
# v.transaction_open? #
|
101
|
-
# v.gsub!(/you/, "HAL") #
|
131
|
+
# v.transaction_open? # -> true
|
132
|
+
# v.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
102
133
|
#
|
103
|
-
# v.commit_transaction #
|
104
|
-
# v.transaction_open? #
|
105
|
-
# v.abort_transaction #
|
106
|
-
# v.transaction_open? #
|
134
|
+
# v.commit_transaction # -> "Hello, HAL."
|
135
|
+
# v.transaction_open? # -> true
|
136
|
+
# v.abort_transaction # -> "Hello, you."
|
137
|
+
# v.transaction_open? # -> false
|
107
138
|
#
|
108
139
|
# == Named Transaction Usage
|
109
|
-
# v = "Hello, you." #
|
110
|
-
# v.extend(Transaction::Simple) #
|
140
|
+
# v = "Hello, you." # -> "Hello, you."
|
141
|
+
# v.extend(Transaction::Simple) # -> "Hello, you."
|
111
142
|
#
|
112
|
-
# v.start_transaction(:first) #
|
113
|
-
# v.transaction_open? #
|
114
|
-
# v.transaction_open?(:first) #
|
115
|
-
# v.transaction_open?(:second) #
|
116
|
-
# v.gsub!(/you/, "world") #
|
143
|
+
# v.start_transaction(:first) # -> ... (a Marshal string)
|
144
|
+
# v.transaction_open? # -> true
|
145
|
+
# v.transaction_open?(:first) # -> true
|
146
|
+
# v.transaction_open?(:second) # -> false
|
147
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
117
148
|
#
|
118
|
-
# v.start_transaction(:second) #
|
119
|
-
# v.gsub!(/world/, "HAL") #
|
120
|
-
# v.rewind_transaction(:first) #
|
121
|
-
# v.transaction_open? #
|
122
|
-
# v.transaction_open?(:first) #
|
123
|
-
# v.transaction_open?(:second) #
|
149
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
150
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
151
|
+
# v.rewind_transaction(:first) # -> "Hello, you."
|
152
|
+
# v.transaction_open? # -> true
|
153
|
+
# v.transaction_open?(:first) # -> true
|
154
|
+
# v.transaction_open?(:second) # -> false
|
124
155
|
#
|
125
|
-
# v.gsub!(/you/, "world") #
|
126
|
-
# v.start_transaction(:second) #
|
127
|
-
# v.gsub!(/world/, "HAL") #
|
128
|
-
# v.transaction_name #
|
129
|
-
# v.abort_transaction(:first) #
|
130
|
-
# v.transaction_open? #
|
156
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
157
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
158
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
159
|
+
# v.transaction_name # -> :second
|
160
|
+
# v.abort_transaction(:first) # -> "Hello, you."
|
161
|
+
# v.transaction_open? # -> false
|
131
162
|
#
|
132
|
-
# v.start_transaction(:first) #
|
133
|
-
# v.gsub!(/you/, "world") #
|
134
|
-
# v.start_transaction(:second) #
|
135
|
-
# v.gsub!(/world/, "HAL") #
|
163
|
+
# v.start_transaction(:first) # -> ... (a Marshal string)
|
164
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
165
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
166
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
136
167
|
#
|
137
|
-
# v.commit_transaction(:first) #
|
138
|
-
# v.transaction_open? #
|
168
|
+
# v.commit_transaction(:first) # -> "Hello, HAL."
|
169
|
+
# v.transaction_open? # -> false
|
139
170
|
#
|
140
171
|
# == Block Usage
|
141
|
-
#
|
142
|
-
#
|
143
|
-
# v = "Hello, you." # => "Hello, you."
|
172
|
+
# v = "Hello, you." # -> "Hello, you."
|
144
173
|
# Transaction::Simple.start(v) do |tv|
|
145
174
|
# # v has been extended with Transaction::Simple and an unnamed
|
146
175
|
# # transaction has been started.
|
147
|
-
# tv.transaction_open? #
|
148
|
-
# tv.gsub!(/you/, "world") #
|
176
|
+
# tv.transaction_open? # -> true
|
177
|
+
# tv.gsub!(/you/, "world") # -> "Hello, world."
|
149
178
|
#
|
150
|
-
# tv.rewind_transaction #
|
151
|
-
# tv.transaction_open? #
|
179
|
+
# tv.rewind_transaction # -> "Hello, you."
|
180
|
+
# tv.transaction_open? # -> true
|
152
181
|
#
|
153
|
-
# tv.gsub!(/you/, "HAL") #
|
182
|
+
# tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
154
183
|
# # The following breaks out of the transaction block after
|
155
184
|
# # aborting the transaction.
|
156
|
-
# tv.abort_transaction #
|
185
|
+
# tv.abort_transaction # -> "Hello, you."
|
157
186
|
# end
|
158
187
|
# # v still has Transaction::Simple applied from here on out.
|
159
|
-
# v.transaction_open? #
|
188
|
+
# v.transaction_open? # -> false
|
160
189
|
#
|
161
190
|
# Transaction::Simple.start(v) do |tv|
|
162
|
-
# tv.start_transaction #
|
191
|
+
# tv.start_transaction # -> ... (a Marshal string)
|
163
192
|
#
|
164
|
-
# tv.transaction_open? #
|
165
|
-
# tv.gsub!(/you/, "HAL") #
|
193
|
+
# tv.transaction_open? # -> true
|
194
|
+
# tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
166
195
|
#
|
167
196
|
# # If #commit_transaction were called without having started a
|
168
197
|
# # second transaction, then it would break out of the transaction
|
169
198
|
# # block after committing the transaction.
|
170
|
-
# tv.commit_transaction #
|
171
|
-
# tv.transaction_open? #
|
172
|
-
# tv.abort_transaction #
|
199
|
+
# tv.commit_transaction # -> "Hello, HAL."
|
200
|
+
# tv.transaction_open? # -> true
|
201
|
+
# tv.abort_transaction # -> "Hello, you."
|
173
202
|
# end
|
174
|
-
# v.transaction_open? #
|
203
|
+
# v.transaction_open? # -> false
|
175
204
|
#
|
176
205
|
# == Named Transaction Usage
|
177
|
-
# v = "Hello, you." #
|
178
|
-
# v.extend(Transaction::Simple) #
|
206
|
+
# v = "Hello, you." # -> "Hello, you."
|
207
|
+
# v.extend(Transaction::Simple) # -> "Hello, you."
|
179
208
|
#
|
180
|
-
# v.start_transaction(:first) #
|
181
|
-
# v.transaction_open? #
|
182
|
-
# v.transaction_open?(:first) #
|
183
|
-
# v.transaction_open?(:second) #
|
184
|
-
# v.gsub!(/you/, "world") #
|
209
|
+
# v.start_transaction(:first) # -> ... (a Marshal string)
|
210
|
+
# v.transaction_open? # -> true
|
211
|
+
# v.transaction_open?(:first) # -> true
|
212
|
+
# v.transaction_open?(:second) # -> false
|
213
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
185
214
|
#
|
186
|
-
# v.start_transaction(:second) #
|
187
|
-
# v.gsub!(/world/, "HAL") #
|
188
|
-
# v.rewind_transaction(:first) #
|
189
|
-
# v.transaction_open? #
|
190
|
-
# v.transaction_open?(:first) #
|
191
|
-
# v.transaction_open?(:second) #
|
215
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
216
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
217
|
+
# v.rewind_transaction(:first) # -> "Hello, you."
|
218
|
+
# v.transaction_open? # -> true
|
219
|
+
# v.transaction_open?(:first) # -> true
|
220
|
+
# v.transaction_open?(:second) # -> false
|
192
221
|
#
|
193
|
-
# v.gsub!(/you/, "world") #
|
194
|
-
# v.start_transaction(:second) #
|
195
|
-
# v.gsub!(/world/, "HAL") #
|
196
|
-
# v.transaction_name #
|
197
|
-
# v.abort_transaction(:first) #
|
198
|
-
# v.transaction_open? #
|
222
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
223
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
224
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
225
|
+
# v.transaction_name # -> :second
|
226
|
+
# v.abort_transaction(:first) # -> "Hello, you."
|
227
|
+
# v.transaction_open? # -> false
|
199
228
|
#
|
200
|
-
# v.start_transaction(:first) #
|
201
|
-
# v.gsub!(/you/, "world") #
|
202
|
-
# v.start_transaction(:second) #
|
203
|
-
# v.gsub!(/world/, "HAL") #
|
229
|
+
# v.start_transaction(:first) # -> ... (a Marshal string)
|
230
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
231
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
232
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
204
233
|
#
|
205
|
-
# v.commit_transaction(:first) #
|
206
|
-
# v.transaction_open? #
|
234
|
+
# v.commit_transaction(:first) # -> "Hello, HAL."
|
235
|
+
# v.transaction_open? # -> false
|
207
236
|
#
|
208
|
-
# ==
|
237
|
+
# == Thread Safety
|
238
|
+
# Threadsafe version of Transaction::Simple and
|
239
|
+
# Transaction::Simple::Group exist; these are loaded from
|
240
|
+
# 'transaction/simple/threadsafe' and
|
241
|
+
# 'transaction/simple/threadsafe/group', respectively, and are
|
242
|
+
# represented in Ruby code as Transaction::Simple::ThreadSafe and
|
243
|
+
# Transaction::Simple::ThreadSafe::Group, respectively.
|
209
244
|
#
|
245
|
+
# == Contraindications
|
210
246
|
# While Transaction::Simple is very useful, it has some severe
|
211
247
|
# limitations that must be understood. Transaction::Simple:
|
212
248
|
#
|
213
249
|
# * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed
|
214
|
-
# cannot use Transaction::Simple.
|
250
|
+
# cannot use Transaction::Simple. In my experience, this affects
|
251
|
+
# singleton objects more often than any other object. It may be that
|
252
|
+
# Ruby 2.0 will solve this problem.
|
215
253
|
# * does not manage resources. Resources external to the object and its
|
216
254
|
# instance variables are not managed at all. However, all instance
|
217
255
|
# variables and objects "belonging" to those instance variables are
|
@@ -224,7 +262,7 @@ module Transaction
|
|
224
262
|
# multi-threaded applications. If thread safety and atomicity is
|
225
263
|
# absolutely required, use Transaction::Simple::ThreadSafe, which uses
|
226
264
|
# a Mutex object to synchronize the accesses on the object during the
|
227
|
-
#
|
265
|
+
# transaction operations.
|
228
266
|
# * does not necessarily maintain Object#__id__ values on rewind or
|
229
267
|
# abort. This may change for future versions that will be Ruby 1.8 or
|
230
268
|
# better *only*. Certain objects that support #replace will maintain
|
@@ -233,20 +271,34 @@ module Transaction
|
|
233
271
|
# objects.
|
234
272
|
#
|
235
273
|
module Simple
|
236
|
-
|
274
|
+
TRANSACTION_SIMPLE_VERSION = '1.3.0'
|
237
275
|
|
238
276
|
# Sets the Transaction::Simple debug object. It must respond to #<<.
|
239
277
|
# Sets the transaction debug object. Debugging will be performed
|
240
278
|
# automatically if there's a debug object. The generic transaction
|
241
279
|
# error class.
|
242
280
|
def self.debug_io=(io)
|
243
|
-
|
244
|
-
|
281
|
+
if io.nil?
|
282
|
+
@tdi = nil
|
283
|
+
@debugging = false
|
284
|
+
else
|
285
|
+
unless io.respond_to?(:<<)
|
286
|
+
raise TransactionError, Messages[:bad_debug_object]
|
287
|
+
end
|
288
|
+
@tdi = io
|
289
|
+
@debugging = true
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Returns +true+ if we are debugging.
|
294
|
+
def self.debugging?
|
295
|
+
@debugging
|
245
296
|
end
|
246
297
|
|
247
298
|
# Returns the Transaction::Simple debug object. It must respond to
|
248
299
|
# #<<.
|
249
300
|
def self.debug_io
|
301
|
+
@tdi ||= ""
|
250
302
|
@tdi
|
251
303
|
end
|
252
304
|
|
@@ -257,10 +309,16 @@ module Transaction
|
|
257
309
|
# transaction that responds to +name+ open.
|
258
310
|
def transaction_open?(name = nil)
|
259
311
|
if name.nil?
|
260
|
-
|
312
|
+
if Transaction::Simple.debugging?
|
313
|
+
Transaction::Simple.debug_io << "Transaction " <<
|
314
|
+
"[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
|
315
|
+
end
|
261
316
|
return (not @__transaction_checkpoint__.nil?)
|
262
317
|
else
|
263
|
-
|
318
|
+
if Transaction::Simple.debugging?
|
319
|
+
Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " <<
|
320
|
+
"[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
|
321
|
+
end
|
264
322
|
return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
|
265
323
|
end
|
266
324
|
end
|
@@ -268,9 +326,18 @@ module Transaction
|
|
268
326
|
# Returns the current name of the transaction. Transactions not
|
269
327
|
# explicitly named are named +nil+.
|
270
328
|
def transaction_name
|
271
|
-
|
272
|
-
|
273
|
-
|
329
|
+
if @__transaction_checkpoint__.nil?
|
330
|
+
raise TransactionError, Messages[:no_transaction_open]
|
331
|
+
end
|
332
|
+
if Transaction::Simple.debugging?
|
333
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
|
334
|
+
"Transaction Name: #{@__transaction_names__[-1].inspect}\n"
|
335
|
+
end
|
336
|
+
if @__transaction_names__[-1].kind_of?(String)
|
337
|
+
@__transaction_names__[-1].dup
|
338
|
+
else
|
339
|
+
@__transaction_names__[-1]
|
340
|
+
end
|
274
341
|
end
|
275
342
|
|
276
343
|
# Starts a transaction. Stores the current object state. If a
|
@@ -283,16 +350,22 @@ module Transaction
|
|
283
350
|
|
284
351
|
if name.nil?
|
285
352
|
@__transaction_names__ << nil
|
286
|
-
ss = ""
|
353
|
+
ss = "" if Transaction::Simple.debugging?
|
287
354
|
else
|
288
|
-
|
355
|
+
if @__transaction_names__.include?(name)
|
356
|
+
raise TransactionError, Messages[:unique_names]
|
357
|
+
end
|
358
|
+
name = name.dup.freeze if name.kind_of?(String)
|
289
359
|
@__transaction_names__ << name
|
290
|
-
ss = "(#{name.inspect})"
|
360
|
+
ss = "(#{name.inspect})" if Transaction::Simple.debugging?
|
291
361
|
end
|
292
362
|
|
293
363
|
@__transaction_level__ += 1
|
294
364
|
|
295
|
-
|
365
|
+
if Transaction::Simple.debugging?
|
366
|
+
Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " <<
|
367
|
+
"Start Transaction#{ss}\n"
|
368
|
+
end
|
296
369
|
|
297
370
|
@__transaction_checkpoint__ = Marshal.dump(self)
|
298
371
|
end
|
@@ -301,23 +374,43 @@ module Transaction
|
|
301
374
|
# intervening transactions will be aborted and the named transaction
|
302
375
|
# will be rewound. Otherwise, only the current transaction is rewound.
|
303
376
|
def rewind_transaction(name = nil)
|
304
|
-
|
377
|
+
if @__transaction_checkpoint__.nil?
|
378
|
+
raise TransactionError, Messages[:cannot_rewind_no_transaction]
|
379
|
+
end
|
380
|
+
|
381
|
+
# Check to see if we are trying to rewind a transaction that is
|
382
|
+
# outside of the current transaction block.
|
383
|
+
if @__transaction_block__ and name
|
384
|
+
nix = @__transaction_names__.index(name) + 1
|
385
|
+
if nix < @__transaction_block__
|
386
|
+
raise TransactionError, Messages[:cannot_rewind_transaction_before_block]
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
305
390
|
if name.nil?
|
306
391
|
__rewind_this_transaction
|
307
|
-
ss = ""
|
392
|
+
ss = "" if Transaction::Simple.debugging?
|
308
393
|
else
|
309
|
-
|
310
|
-
|
394
|
+
unless @__transaction_names__.include?(name)
|
395
|
+
raise TransactionError, Messages[:cannot_rewind_named_transaction] % name.inspect
|
396
|
+
end
|
397
|
+
ss = "(#{name})" if Transaction::Simple.debugging?
|
311
398
|
|
312
399
|
while @__transaction_names__[-1] != name
|
313
400
|
@__transaction_checkpoint__ = __rewind_this_transaction
|
314
|
-
|
401
|
+
if Transaction::Simple.debugging?
|
402
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
|
403
|
+
"Rewind Transaction#{ss}\n"
|
404
|
+
end
|
315
405
|
@__transaction_level__ -= 1
|
316
406
|
@__transaction_names__.pop
|
317
407
|
end
|
318
408
|
__rewind_this_transaction
|
319
409
|
end
|
320
|
-
|
410
|
+
if Transaction::Simple.debugging?
|
411
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
|
412
|
+
"Rewind Transaction#{ss}\n"
|
413
|
+
end
|
321
414
|
self
|
322
415
|
end
|
323
416
|
|
@@ -331,14 +424,18 @@ module Transaction
|
|
331
424
|
# (Transaction::Simple.start), then the execution of the block will be
|
332
425
|
# halted with +break+ +self+.
|
333
426
|
def abort_transaction(name = nil)
|
334
|
-
|
427
|
+
if @__transaction_checkpoint__.nil?
|
428
|
+
raise TransactionError, Messages[:cannot_abort_no_transaction]
|
429
|
+
end
|
335
430
|
|
336
431
|
# Check to see if we are trying to abort a transaction that is
|
337
432
|
# outside of the current transaction block. Otherwise, raise
|
338
433
|
# TransactionAborted if they are the same.
|
339
434
|
if @__transaction_block__ and name
|
340
435
|
nix = @__transaction_names__.index(name) + 1
|
341
|
-
|
436
|
+
if nix < @__transaction_block__
|
437
|
+
raise TransactionError, Messages[:cannot_abort_transaction_before_block]
|
438
|
+
end
|
342
439
|
|
343
440
|
raise TransactionAborted if @__transaction_block__ == nix
|
344
441
|
end
|
@@ -348,8 +445,9 @@ module Transaction
|
|
348
445
|
if name.nil?
|
349
446
|
__abort_transaction(name)
|
350
447
|
else
|
351
|
-
|
352
|
-
|
448
|
+
unless @__transaction_names__.include?(name)
|
449
|
+
raise TransactionError, Messages[:cannot_abort_named_transaction] % name.inspect
|
450
|
+
end
|
353
451
|
__abort_transaction(name) while @__transaction_names__.include?(name)
|
354
452
|
end
|
355
453
|
self
|
@@ -362,14 +460,19 @@ module Transaction
|
|
362
460
|
# transactions, then all transactions are closed and committed until
|
363
461
|
# the named transaction is reached.
|
364
462
|
def commit_transaction(name = nil)
|
365
|
-
|
463
|
+
if @__transaction_checkpoint__.nil?
|
464
|
+
raise TransactionError, Messages[:cannot_commit_no_transaction]
|
465
|
+
end
|
466
|
+
@__transaction_block__ ||= nil
|
366
467
|
|
367
468
|
# Check to see if we are trying to commit a transaction that is
|
368
469
|
# outside of the current transaction block. Otherwise, raise
|
369
470
|
# TransactionCommitted if they are the same.
|
370
471
|
if @__transaction_block__ and name
|
371
472
|
nix = @__transaction_names__.index(name) + 1
|
372
|
-
|
473
|
+
if nix < @__transaction_block__
|
474
|
+
raise TransactionError, Messages[:cannot_commit_transaction_before_block]
|
475
|
+
end
|
373
476
|
|
374
477
|
raise TransactionCommitted if @__transaction_block__ == nix
|
375
478
|
end
|
@@ -377,18 +480,29 @@ module Transaction
|
|
377
480
|
raise TransactionCommitted if @__transaction_block__ == @__transaction_level__
|
378
481
|
|
379
482
|
if name.nil?
|
380
|
-
ss = ""
|
483
|
+
ss = "" if Transaction::Simple.debugging?
|
381
484
|
__commit_transaction
|
382
|
-
|
485
|
+
if Transaction::Simple.debugging?
|
486
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
|
487
|
+
"Commit Transaction#{ss}\n"
|
488
|
+
end
|
383
489
|
else
|
384
|
-
|
385
|
-
|
490
|
+
unless @__transaction_names__.include?(name)
|
491
|
+
raise TransactionError, Messages[:cannot_commit_named_transaction] % name.inspect
|
492
|
+
end
|
493
|
+
ss = "(#{name})" if Transaction::Simple.debugging?
|
386
494
|
|
387
495
|
while @__transaction_names__[-1] != name
|
388
|
-
|
496
|
+
if Transaction::Simple.debugging?
|
497
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
|
498
|
+
"Commit Transaction#{ss}\n"
|
499
|
+
end
|
389
500
|
__commit_transaction
|
390
501
|
end
|
391
|
-
|
502
|
+
if Transaction::Simple.debugging?
|
503
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
|
504
|
+
"Commit Transaction#{ss}\n"
|
505
|
+
end
|
392
506
|
__commit_transaction
|
393
507
|
end
|
394
508
|
|
@@ -421,9 +535,20 @@ module Transaction
|
|
421
535
|
end
|
422
536
|
end
|
423
537
|
|
538
|
+
# Allows specific variables to be excluded from transaction support.
|
539
|
+
# Must be done after extending the object but before starting the
|
540
|
+
# first transaction on the object.
|
541
|
+
#
|
542
|
+
# vv.transaction_exclusions << "@io"
|
543
|
+
def transaction_exclusions
|
544
|
+
@transaction_exclusions ||= []
|
545
|
+
end
|
546
|
+
|
424
547
|
class << self
|
425
548
|
def __common_start(name, vars, &block)
|
426
|
-
|
549
|
+
if vars.empty?
|
550
|
+
raise TransactionError, Messages[:cannot_start_empty_block_transaction]
|
551
|
+
end
|
427
552
|
|
428
553
|
if block
|
429
554
|
begin
|
@@ -436,7 +561,7 @@ module Transaction
|
|
436
561
|
vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
|
437
562
|
end
|
438
563
|
|
439
|
-
yield
|
564
|
+
yield(*vars)
|
440
565
|
rescue TransactionAborted
|
441
566
|
vars.each do |vv|
|
442
567
|
if name.nil? and vv.transaction_open?
|
@@ -490,12 +615,15 @@ module Transaction
|
|
490
615
|
@__transaction_checkpoint__ = __rewind_this_transaction
|
491
616
|
|
492
617
|
if name.nil?
|
493
|
-
ss = ""
|
618
|
+
ss = "" if Transaction::Simple.debugging?
|
494
619
|
else
|
495
|
-
ss = "(#{name.inspect})"
|
620
|
+
ss = "(#{name.inspect})" if Transaction::Simple.debugging?
|
496
621
|
end
|
497
622
|
|
498
|
-
|
623
|
+
if Transaction::Simple.debugging?
|
624
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
|
625
|
+
"Abort Transaction#{ss}\n"
|
626
|
+
end
|
499
627
|
@__transaction_level__ -= 1
|
500
628
|
@__transaction_names__.pop
|
501
629
|
if @__transaction_level__ < 1
|
@@ -508,27 +636,37 @@ module Transaction
|
|
508
636
|
SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
|
509
637
|
|
510
638
|
def __rewind_this_transaction #:nodoc:
|
511
|
-
|
639
|
+
rr = Marshal.restore(@__transaction_checkpoint__)
|
512
640
|
|
513
641
|
begin
|
514
|
-
self.replace(
|
642
|
+
self.replace(rr) if respond_to?(:replace)
|
515
643
|
rescue
|
516
644
|
nil
|
517
645
|
end
|
518
646
|
|
519
|
-
|
520
|
-
next if SKIP_TRANSACTION_VARS.include?(
|
647
|
+
rr.instance_variables.each do |vv|
|
648
|
+
next if SKIP_TRANSACTION_VARS.include?(vv)
|
649
|
+
next if self.transaction_exclusions.include?(vv)
|
521
650
|
if respond_to?(:instance_variable_get)
|
522
|
-
instance_variable_set(
|
651
|
+
instance_variable_set(vv, rr.instance_variable_get(vv))
|
523
652
|
else
|
524
|
-
instance_eval(%q|#{
|
653
|
+
instance_eval(%q|#{vv} = rr.instance_eval("#{vv}")|)
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
new_ivar = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS
|
658
|
+
new_ivar.each do |vv|
|
659
|
+
if respond_to?(:instance_variable_set)
|
660
|
+
instance_variable_set(vv, nil)
|
661
|
+
else
|
662
|
+
instance_eval(%q|#{vv} = nil|)
|
525
663
|
end
|
526
664
|
end
|
527
665
|
|
528
666
|
if respond_to?(:instance_variable_get)
|
529
|
-
|
667
|
+
rr.instance_variable_get(TRANSACTION_CHECKPOINT)
|
530
668
|
else
|
531
|
-
|
669
|
+
rr.instance_eval(TRANSACTION_CHECKPOINT)
|
532
670
|
end
|
533
671
|
end
|
534
672
|
|
@@ -541,51 +679,15 @@ module Transaction
|
|
541
679
|
|
542
680
|
@__transaction_level__ -= 1
|
543
681
|
@__transaction_names__.pop
|
682
|
+
|
544
683
|
if @__transaction_level__ < 1
|
545
684
|
@__transaction_level__ = 0
|
546
685
|
@__transaction_names__ = []
|
547
686
|
end
|
548
687
|
end
|
549
688
|
|
550
|
-
private :__abort_transaction
|
551
|
-
|
552
|
-
|
553
|
-
# Thread-safe simple object transaction support for Ruby.
|
554
|
-
# Transaction::Simple::ThreadSafe is used in the same way as
|
555
|
-
# Transaction::Simple. Transaction::Simple::ThreadSafe uses a Mutex
|
556
|
-
# object to ensure atomicity at the cost of performance in threaded
|
557
|
-
# applications.
|
558
|
-
#
|
559
|
-
# Transaction::Simple::ThreadSafe will not wait to obtain a lock; if the
|
560
|
-
# lock cannot be obtained immediately, a
|
561
|
-
# Transaction::TransactionThreadError will be raised.
|
562
|
-
#
|
563
|
-
# Thanks to Mauricio Fern�ndez for help with getting this part working.
|
564
|
-
module ThreadSafe
|
565
|
-
VERSION = '1.2.0'
|
566
|
-
|
567
|
-
include Transaction::Simple
|
568
|
-
|
569
|
-
SKIP_TRANSACTION_VARS = Transaction::Simple::SKIP_TRANSACTION_VARS.dup #:nodoc:
|
570
|
-
SKIP_TRANSACTION_VARS << "@__transaction_mutex__"
|
571
|
-
|
572
|
-
Transaction::Simple.instance_methods(false) do |meth|
|
573
|
-
next if meth == "transaction"
|
574
|
-
arg = "(name = nil)" unless meth == "transaction_name"
|
575
|
-
module_eval <<-EOS
|
576
|
-
def #{meth}#{arg}
|
577
|
-
if (@__transaction_mutex__ ||= Mutex.new).try_lock
|
578
|
-
result = super
|
579
|
-
@__transaction_mutex__.unlock
|
580
|
-
return result
|
581
|
-
else
|
582
|
-
raise TransactionThreadError, "Transaction Error: Cannot obtain lock for ##{meth}"
|
583
|
-
end
|
584
|
-
ensure
|
585
|
-
@__transaction_mutex__.unlock
|
586
|
-
end
|
587
|
-
EOS
|
588
|
-
end
|
589
|
-
end
|
689
|
+
private :__abort_transaction
|
690
|
+
private :__rewind_this_transaction
|
691
|
+
private :__commit_transaction
|
590
692
|
end
|
591
693
|
end
|