transaction-simple 1.2.0 → 1.3.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/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
|