transaction-simple 1.2.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 +20 -0
- data/Changelog +20 -0
- data/Install +2 -0
- data/README +110 -0
- data/Rakefile +120 -0
- data/Readme +110 -0
- data/lib/transaction/simple.rb +591 -0
- data/tests/tests.rb +402 -0
- metadata +55 -0
data/ChangeLog
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$Id: Changelog,v 1.2 2004/09/14 18:46:15 austin Exp $
|
2
|
+
|
3
|
+
== Transaction::Simple 1.2.0
|
4
|
+
* Added a RubyGem.
|
5
|
+
* Added a block form of Transaction::Simple.
|
6
|
+
|
7
|
+
== Transaction::Simple 1.1.1
|
8
|
+
* Cleaned up some documentation.
|
9
|
+
|
10
|
+
== Transaction::Simple 1.1
|
11
|
+
* Added Transaction::Simple::ThreadSafe for truly atomic and thread-safe
|
12
|
+
transactions.
|
13
|
+
* Fixed the description of Transaction::Simple to note that it is *not* atomic
|
14
|
+
because it is not necessarily thread-safe.
|
15
|
+
* Added support for named transactions. Named transactions can be used to make
|
16
|
+
checkpoints that can be committed, aborted, or rewound without explicitly
|
17
|
+
committing, aborting, or rewinding the intervening transactions.
|
18
|
+
|
19
|
+
== Transaction::Simple 1.0
|
20
|
+
* Created. Initial release.
|
data/Changelog
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$Id: Changelog,v 1.2 2004/09/14 18:46:15 austin Exp $
|
2
|
+
|
3
|
+
== Transaction::Simple 1.2.0
|
4
|
+
* Added a RubyGem.
|
5
|
+
* Added a block form of Transaction::Simple.
|
6
|
+
|
7
|
+
== Transaction::Simple 1.1.1
|
8
|
+
* Cleaned up some documentation.
|
9
|
+
|
10
|
+
== Transaction::Simple 1.1
|
11
|
+
* Added Transaction::Simple::ThreadSafe for truly atomic and thread-safe
|
12
|
+
transactions.
|
13
|
+
* Fixed the description of Transaction::Simple to note that it is *not* atomic
|
14
|
+
because it is not necessarily thread-safe.
|
15
|
+
* Added support for named transactions. Named transactions can be used to make
|
16
|
+
checkpoints that can be committed, aborted, or rewound without explicitly
|
17
|
+
committing, aborting, or rewinding the intervening transactions.
|
18
|
+
|
19
|
+
== Transaction::Simple 1.0
|
20
|
+
* Created. Initial release.
|
data/Install
ADDED
data/README
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
Transaction::Simple for Ruby
|
2
|
+
Simple object transaction support for Ruby
|
3
|
+
|
4
|
+
Introduction
|
5
|
+
------------
|
6
|
+
Transaction::Simple provides a generic way to add active transactional support
|
7
|
+
to objects. The transaction methods added by this module will work with most
|
8
|
+
objects, excluding those that cannot be Marshal-ed (bindings, procedure
|
9
|
+
objects, IO instances, or singleton objects).
|
10
|
+
|
11
|
+
The transactions supported by Transaction::Simple are not backend transaction;
|
12
|
+
that is, they have nothing to do with any sort of data store. They are "live"
|
13
|
+
transactions occurring in memory and in the object itself. This is to allow
|
14
|
+
"test" changes to be made to an object before making the changes permanent.
|
15
|
+
|
16
|
+
Transaction::Simple can handle an "infinite" number of transactional levels
|
17
|
+
(limited only by memory). If I open two transactions, commit the first, but
|
18
|
+
abort the second, the object will revert to the original version.
|
19
|
+
|
20
|
+
Transaction::Simple supports "named" transactions, so that multiple levels of
|
21
|
+
transactions can be committed, aborted, or rewound by referring to the
|
22
|
+
appropriate name of the transaction. Names may be any object except nil.
|
23
|
+
|
24
|
+
Copyright: Copyright � 2003 by Austin Ziegler
|
25
|
+
Version: 1.11
|
26
|
+
Licence: MIT-Style
|
27
|
+
|
28
|
+
Thanks to David Black and Mauricio Fern�ndez for their help with this library.
|
29
|
+
|
30
|
+
Usage
|
31
|
+
-----
|
32
|
+
include 'transaction/simple'
|
33
|
+
|
34
|
+
v = "Hello, you." # => "Hello, you."
|
35
|
+
v.extend(Transaction::Simple) # => "Hello, you."
|
36
|
+
|
37
|
+
v.start_transaction # => ... (a Marshal string)
|
38
|
+
v.transaction_open? # => true
|
39
|
+
v.gsub!(/you/, "world") # => "Hello, world."
|
40
|
+
|
41
|
+
v.rewind_transaction # => "Hello, you."
|
42
|
+
v.transaction_open? # => true
|
43
|
+
|
44
|
+
v.gsub!(/you/, "HAL") # => "Hello, HAL."
|
45
|
+
v.abort_transaction # => "Hello, you."
|
46
|
+
v.transaction_open? # => false
|
47
|
+
|
48
|
+
v.start_transaction # => ... (a Marshal string)
|
49
|
+
v.start_transaction # => ... (a Marshal string)
|
50
|
+
|
51
|
+
v.transaction_open? # => true
|
52
|
+
v.gsub!(/you/, "HAL") # => "Hello, HAL."
|
53
|
+
|
54
|
+
v.commit_transaction # => "Hello, HAL."
|
55
|
+
v.transaction_open? # => true
|
56
|
+
v.abort_transaction # => "Hello, you."
|
57
|
+
v.transaction_open? # => false
|
58
|
+
|
59
|
+
Named Transaction Usage
|
60
|
+
-----------------------
|
61
|
+
v = "Hello, you." # => "Hello, you."
|
62
|
+
v.extend(Transaction::Simple) # => "Hello, you."
|
63
|
+
|
64
|
+
v.start_transaction(:first) # => ... (a Marshal string)
|
65
|
+
v.transaction_open? # => true
|
66
|
+
v.transaction_open?(:first) # => true
|
67
|
+
v.transaction_open?(:second) # => false
|
68
|
+
v.gsub!(/you/, "world") # => "Hello, world."
|
69
|
+
|
70
|
+
v.start_transaction(:second) # => ... (a Marshal string)
|
71
|
+
v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
72
|
+
v.rewind_transaction(:first) # => "Hello, you."
|
73
|
+
v.transaction_open? # => true
|
74
|
+
v.transaction_open?(:first) # => true
|
75
|
+
v.transaction_open?(:second) # => false
|
76
|
+
|
77
|
+
v.gsub!(/you/, "world") # => "Hello, world."
|
78
|
+
v.start_transaction(:second) # => ... (a Marshal string)
|
79
|
+
v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
80
|
+
v.transaction_name # => :second
|
81
|
+
v.abort_transaction(:first) # => "Hello, you."
|
82
|
+
v.transaction_open? # => false
|
83
|
+
|
84
|
+
v.start_transaction(:first) # => ... (a Marshal string)
|
85
|
+
v.gsub!(/you/, "world") # => "Hello, world."
|
86
|
+
v.start_transaction(:second) # => ... (a Marshal string)
|
87
|
+
v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
88
|
+
|
89
|
+
v.commit_transaction(:first) # => "Hello, HAL."
|
90
|
+
v.transaction_open? # => false
|
91
|
+
|
92
|
+
Contraindications
|
93
|
+
-----------------
|
94
|
+
While Transaction::Simple is very useful, it has some severe limitations that
|
95
|
+
must be understood. Transaction::Simple:
|
96
|
+
|
97
|
+
* uses Marshal. Thus, any object which cannot be Marshal-ed cannot use
|
98
|
+
Transaction::Simple.
|
99
|
+
* does not manage resources. Resources external to the object and its instance
|
100
|
+
variables are not managed at all. However, all instance variables and
|
101
|
+
objects "belonging" to those instance variables are managed. If there are
|
102
|
+
object reference counts to be handled, Transaction::Simple will probably
|
103
|
+
cause problems.
|
104
|
+
* is not thread-safe. In the ACID ("atomic, consistent, isolated, durable")
|
105
|
+
test, Transaction::Simple provides C and D, but it is up to the user of
|
106
|
+
Transaction::Simple to provide isolation. Transactions should be considered
|
107
|
+
"critical sections" in multi-threaded applications. Thread safety can be
|
108
|
+
ensured with Transaction::Simple::ThreadSafe.
|
109
|
+
* does not maintain Object#__id__ values on rewind or abort. This may change
|
110
|
+
for future versions that will be Ruby 1.8 or better only.
|
data/Rakefile
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
#! /usr/bin/env rake
|
2
|
+
$LOAD_PATH.unshift('lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rake/contrib/rubyforgepublisher'
|
7
|
+
require 'transaction/simple'
|
8
|
+
require 'archive/tar/minitar'
|
9
|
+
require 'zlib'
|
10
|
+
|
11
|
+
DISTDIR = "transaction-simple-#{Transaction::Simple::VERSION}"
|
12
|
+
TARDIST = "../#{DISTDIR}.tar.gz"
|
13
|
+
|
14
|
+
DATE_RE = %r<(\d{4})[./-]?(\d{2})[./-]?(\d{2})(?:[\sT]?(\d{2})[:.]?(\d{2})[:.]?(\d{2})?)?>
|
15
|
+
|
16
|
+
if ENV['RELEASE_DATE']
|
17
|
+
year, month, day, hour, minute, second = DATE_RE.match(ENV['RELEASE_DATE']).captures
|
18
|
+
year ||= 0
|
19
|
+
month ||= 0
|
20
|
+
day ||= 0
|
21
|
+
hour ||= 0
|
22
|
+
minute ||= 0
|
23
|
+
second ||= 0
|
24
|
+
ReleaseDate = Time.mktime(year, month, day, hour, minute, second)
|
25
|
+
else
|
26
|
+
ReleaseDate = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
task :test do |t|
|
30
|
+
require 'test/unit/testsuite'
|
31
|
+
require 'test/unit/ui/console/testrunner'
|
32
|
+
|
33
|
+
runner = Test::Unit::UI::Console::TestRunner
|
34
|
+
|
35
|
+
$LOAD_PATH.unshift('tests')
|
36
|
+
$stderr.puts "Checking for test cases:" if t.verbose
|
37
|
+
Dir['tests/*test*.rb'].each do |testcase|
|
38
|
+
$stderr.puts "\t#{testcase}" if t.verbose
|
39
|
+
load testcase
|
40
|
+
end
|
41
|
+
|
42
|
+
suite = Test::Unit::TestSuite.new("Transaction::Simple")
|
43
|
+
|
44
|
+
ObjectSpace.each_object(Class) do |testcase|
|
45
|
+
suite << testcase.suite if testcase < Test::Unit::TestCase
|
46
|
+
end
|
47
|
+
|
48
|
+
runner.run(suite)
|
49
|
+
end
|
50
|
+
|
51
|
+
spec = eval(File.read("transaction-simple.gemspec"))
|
52
|
+
spec.version = Transaction::Simple::VERSION
|
53
|
+
desc "Build the RubyGem for Transaction::Simple"
|
54
|
+
task :gem => [ :test ]
|
55
|
+
Rake::GemPackageTask.new(spec) do |g|
|
56
|
+
g.need_tar = false
|
57
|
+
g.need_zip = false
|
58
|
+
g.package_dir = ".."
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "Build a Transaction::Simple .tar.gz distribution."
|
62
|
+
task :tar => [ TARDIST ]
|
63
|
+
file TARDIST => [ :test ] do |t|
|
64
|
+
current = File.basename(Dir.pwd)
|
65
|
+
Dir.chdir("..") do
|
66
|
+
begin
|
67
|
+
files = Dir["#{current}/**/*"].select { |dd| dd !~ %r{(?:/CVS/?|~$)} }
|
68
|
+
files.map! do |dd|
|
69
|
+
ddnew = dd.gsub(/^#{current}/, DISTDIR)
|
70
|
+
mtime = ReleaseDate || File.stat(dd).mtime
|
71
|
+
if File.directory?(dd)
|
72
|
+
{ :name => ddnew, :mode => 0755, :dir => true, :mtime => mtime }
|
73
|
+
else
|
74
|
+
if dd =~ %r{bin/}
|
75
|
+
mode = 0755
|
76
|
+
else
|
77
|
+
mode = 0644
|
78
|
+
end
|
79
|
+
data = File.read(dd)
|
80
|
+
{ :name => ddnew, :mode => mode, :data => data, :size => data.size,
|
81
|
+
:mtime => mtime }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
ff = File.open(t.name.gsub(%r{^\.\./}o, ''), "wb")
|
86
|
+
gz = Zlib::GzipWriter.new(ff)
|
87
|
+
tw = Archive::Tar::Minitar::Writer.new(gz)
|
88
|
+
|
89
|
+
files.each do |entry|
|
90
|
+
if entry[:dir]
|
91
|
+
tw.mkdir(entry[:name], entry)
|
92
|
+
else
|
93
|
+
tw.add_file_simple(entry[:name], entry) { |os| os.write(entry[:data]) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
ensure
|
97
|
+
tw.close if tw
|
98
|
+
gz.close if gz
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
task TARDIST => [ :test ]
|
103
|
+
|
104
|
+
def sign(file)
|
105
|
+
begin
|
106
|
+
sh %("C:\\Program Files\\Windows Privacy Tools\\GnuPG\\Gpg.exe" -ba #{file.gsub(%r</>) do '\\'; end})
|
107
|
+
rescue RuntimeError
|
108
|
+
raise unless File.exist?("#{file}.asc")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
task :signtar => [ :tar ] do
|
113
|
+
sign TARDIST
|
114
|
+
end
|
115
|
+
task :signgem => [ :gem ] do
|
116
|
+
sign "../#{DISTDIR}.gem"
|
117
|
+
end
|
118
|
+
|
119
|
+
desc "Build everything."
|
120
|
+
task :default => [ :signtar, :signgem ]
|
data/Readme
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
Transaction::Simple for Ruby
|
2
|
+
Simple object transaction support for Ruby
|
3
|
+
|
4
|
+
Introduction
|
5
|
+
------------
|
6
|
+
Transaction::Simple provides a generic way to add active transactional support
|
7
|
+
to objects. The transaction methods added by this module will work with most
|
8
|
+
objects, excluding those that cannot be Marshal-ed (bindings, procedure
|
9
|
+
objects, IO instances, or singleton objects).
|
10
|
+
|
11
|
+
The transactions supported by Transaction::Simple are not backend transaction;
|
12
|
+
that is, they have nothing to do with any sort of data store. They are "live"
|
13
|
+
transactions occurring in memory and in the object itself. This is to allow
|
14
|
+
"test" changes to be made to an object before making the changes permanent.
|
15
|
+
|
16
|
+
Transaction::Simple can handle an "infinite" number of transactional levels
|
17
|
+
(limited only by memory). If I open two transactions, commit the first, but
|
18
|
+
abort the second, the object will revert to the original version.
|
19
|
+
|
20
|
+
Transaction::Simple supports "named" transactions, so that multiple levels of
|
21
|
+
transactions can be committed, aborted, or rewound by referring to the
|
22
|
+
appropriate name of the transaction. Names may be any object except nil.
|
23
|
+
|
24
|
+
Copyright: Copyright � 2003 by Austin Ziegler
|
25
|
+
Version: 1.11
|
26
|
+
Licence: MIT-Style
|
27
|
+
|
28
|
+
Thanks to David Black and Mauricio Fern�ndez for their help with this library.
|
29
|
+
|
30
|
+
Usage
|
31
|
+
-----
|
32
|
+
include 'transaction/simple'
|
33
|
+
|
34
|
+
v = "Hello, you." # => "Hello, you."
|
35
|
+
v.extend(Transaction::Simple) # => "Hello, you."
|
36
|
+
|
37
|
+
v.start_transaction # => ... (a Marshal string)
|
38
|
+
v.transaction_open? # => true
|
39
|
+
v.gsub!(/you/, "world") # => "Hello, world."
|
40
|
+
|
41
|
+
v.rewind_transaction # => "Hello, you."
|
42
|
+
v.transaction_open? # => true
|
43
|
+
|
44
|
+
v.gsub!(/you/, "HAL") # => "Hello, HAL."
|
45
|
+
v.abort_transaction # => "Hello, you."
|
46
|
+
v.transaction_open? # => false
|
47
|
+
|
48
|
+
v.start_transaction # => ... (a Marshal string)
|
49
|
+
v.start_transaction # => ... (a Marshal string)
|
50
|
+
|
51
|
+
v.transaction_open? # => true
|
52
|
+
v.gsub!(/you/, "HAL") # => "Hello, HAL."
|
53
|
+
|
54
|
+
v.commit_transaction # => "Hello, HAL."
|
55
|
+
v.transaction_open? # => true
|
56
|
+
v.abort_transaction # => "Hello, you."
|
57
|
+
v.transaction_open? # => false
|
58
|
+
|
59
|
+
Named Transaction Usage
|
60
|
+
-----------------------
|
61
|
+
v = "Hello, you." # => "Hello, you."
|
62
|
+
v.extend(Transaction::Simple) # => "Hello, you."
|
63
|
+
|
64
|
+
v.start_transaction(:first) # => ... (a Marshal string)
|
65
|
+
v.transaction_open? # => true
|
66
|
+
v.transaction_open?(:first) # => true
|
67
|
+
v.transaction_open?(:second) # => false
|
68
|
+
v.gsub!(/you/, "world") # => "Hello, world."
|
69
|
+
|
70
|
+
v.start_transaction(:second) # => ... (a Marshal string)
|
71
|
+
v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
72
|
+
v.rewind_transaction(:first) # => "Hello, you."
|
73
|
+
v.transaction_open? # => true
|
74
|
+
v.transaction_open?(:first) # => true
|
75
|
+
v.transaction_open?(:second) # => false
|
76
|
+
|
77
|
+
v.gsub!(/you/, "world") # => "Hello, world."
|
78
|
+
v.start_transaction(:second) # => ... (a Marshal string)
|
79
|
+
v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
80
|
+
v.transaction_name # => :second
|
81
|
+
v.abort_transaction(:first) # => "Hello, you."
|
82
|
+
v.transaction_open? # => false
|
83
|
+
|
84
|
+
v.start_transaction(:first) # => ... (a Marshal string)
|
85
|
+
v.gsub!(/you/, "world") # => "Hello, world."
|
86
|
+
v.start_transaction(:second) # => ... (a Marshal string)
|
87
|
+
v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
88
|
+
|
89
|
+
v.commit_transaction(:first) # => "Hello, HAL."
|
90
|
+
v.transaction_open? # => false
|
91
|
+
|
92
|
+
Contraindications
|
93
|
+
-----------------
|
94
|
+
While Transaction::Simple is very useful, it has some severe limitations that
|
95
|
+
must be understood. Transaction::Simple:
|
96
|
+
|
97
|
+
* uses Marshal. Thus, any object which cannot be Marshal-ed cannot use
|
98
|
+
Transaction::Simple.
|
99
|
+
* does not manage resources. Resources external to the object and its instance
|
100
|
+
variables are not managed at all. However, all instance variables and
|
101
|
+
objects "belonging" to those instance variables are managed. If there are
|
102
|
+
object reference counts to be handled, Transaction::Simple will probably
|
103
|
+
cause problems.
|
104
|
+
* is not thread-safe. In the ACID ("atomic, consistent, isolated, durable")
|
105
|
+
test, Transaction::Simple provides C and D, but it is up to the user of
|
106
|
+
Transaction::Simple to provide isolation. Transactions should be considered
|
107
|
+
"critical sections" in multi-threaded applications. Thread safety can be
|
108
|
+
ensured with Transaction::Simple::ThreadSafe.
|
109
|
+
* does not maintain Object#__id__ values on rewind or abort. This may change
|
110
|
+
for future versions that will be Ruby 1.8 or better only.
|
@@ -0,0 +1,591 @@
|
|
1
|
+
# :title: Transaction::Simple
|
2
|
+
# :main: Transaction::Simple
|
3
|
+
#
|
4
|
+
# == Licence
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
23
|
+
#--
|
24
|
+
# Transaction::Simple
|
25
|
+
# Simple object transaction support for Ruby
|
26
|
+
# Version 1.2.0
|
27
|
+
#
|
28
|
+
# Copyright (c) 2003 - 2004 Austin Ziegler
|
29
|
+
#
|
30
|
+
# $Id: simple.rb,v 1.2 2004/09/14 18:46:15 austin Exp $
|
31
|
+
#++
|
32
|
+
# Required for Transaction::Simple::ThreadSafe
|
33
|
+
require 'thread'
|
34
|
+
|
35
|
+
# The "Transaction" namespace can be used for additional transactional
|
36
|
+
# support objects and modules.
|
37
|
+
module Transaction
|
38
|
+
# A standard exception for transactional errors.
|
39
|
+
class TransactionError < StandardError; end
|
40
|
+
class TransactionAborted < Exception; end
|
41
|
+
class TransactionCommitted < Exception; end
|
42
|
+
# A standard exception for transactional errors involving the acquisition
|
43
|
+
# of locks for Transaction::Simple::ThreadSafe.
|
44
|
+
class TransactionThreadError < StandardError; end
|
45
|
+
|
46
|
+
# = Transaction::Simple for Ruby
|
47
|
+
# Simple object transaction support for Ruby
|
48
|
+
#
|
49
|
+
# == Introduction
|
50
|
+
#
|
51
|
+
# Transaction::Simple provides a generic way to add active transactional
|
52
|
+
# support to objects. The transaction methods added by this module will
|
53
|
+
# work with most objects, excluding those that cannot be
|
54
|
+
# <i>Marshal</i>ed (bindings, procedure objects, IO instances, or
|
55
|
+
# singleton objects).
|
56
|
+
#
|
57
|
+
# The transactions supported by Transaction::Simple are not backed
|
58
|
+
# transactions; that is, they have nothing to do with any sort of data
|
59
|
+
# store. They are "live" transactions occurring in memory and in the
|
60
|
+
# object itself. This is to allow "test" changes to be made to an object
|
61
|
+
# before making the changes permanent.
|
62
|
+
#
|
63
|
+
# Transaction::Simple can handle an "infinite" number of transactional
|
64
|
+
# levels (limited only by memory). If I open two transactions, commit
|
65
|
+
# the first, but abort the second, the object will revert to the
|
66
|
+
# original version.
|
67
|
+
#
|
68
|
+
# Transaction::Simple supports "named" transactions, so that multiple
|
69
|
+
# levels of transactions can be committed, aborted, or rewound by
|
70
|
+
# referring to the appropriate name of the transaction. Names may be any
|
71
|
+
# object *except* +nil+.
|
72
|
+
#
|
73
|
+
# Copyright:: Copyright � 2003 - 2004 by Austin Ziegler
|
74
|
+
# Version:: 1.2
|
75
|
+
# Licence:: MIT-Style
|
76
|
+
#
|
77
|
+
# Thanks to David Black for help with the initial concept that led to
|
78
|
+
# this library.
|
79
|
+
#
|
80
|
+
# == Usage
|
81
|
+
# include 'transaction/simple'
|
82
|
+
#
|
83
|
+
# v = "Hello, you." # => "Hello, you."
|
84
|
+
# v.extend(Transaction::Simple) # => "Hello, you."
|
85
|
+
#
|
86
|
+
# v.start_transaction # => ... (a Marshal string)
|
87
|
+
# v.transaction_open? # => true
|
88
|
+
# v.gsub!(/you/, "world") # => "Hello, world."
|
89
|
+
#
|
90
|
+
# v.rewind_transaction # => "Hello, you."
|
91
|
+
# v.transaction_open? # => true
|
92
|
+
#
|
93
|
+
# v.gsub!(/you/, "HAL") # => "Hello, HAL."
|
94
|
+
# v.abort_transaction # => "Hello, you."
|
95
|
+
# v.transaction_open? # => false
|
96
|
+
#
|
97
|
+
# v.start_transaction # => ... (a Marshal string)
|
98
|
+
# v.start_transaction # => ... (a Marshal string)
|
99
|
+
#
|
100
|
+
# v.transaction_open? # => true
|
101
|
+
# v.gsub!(/you/, "HAL") # => "Hello, HAL."
|
102
|
+
#
|
103
|
+
# v.commit_transaction # => "Hello, HAL."
|
104
|
+
# v.transaction_open? # => true
|
105
|
+
# v.abort_transaction # => "Hello, you."
|
106
|
+
# v.transaction_open? # => false
|
107
|
+
#
|
108
|
+
# == Named Transaction Usage
|
109
|
+
# v = "Hello, you." # => "Hello, you."
|
110
|
+
# v.extend(Transaction::Simple) # => "Hello, you."
|
111
|
+
#
|
112
|
+
# v.start_transaction(:first) # => ... (a Marshal string)
|
113
|
+
# v.transaction_open? # => true
|
114
|
+
# v.transaction_open?(:first) # => true
|
115
|
+
# v.transaction_open?(:second) # => false
|
116
|
+
# v.gsub!(/you/, "world") # => "Hello, world."
|
117
|
+
#
|
118
|
+
# v.start_transaction(:second) # => ... (a Marshal string)
|
119
|
+
# v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
120
|
+
# v.rewind_transaction(:first) # => "Hello, you."
|
121
|
+
# v.transaction_open? # => true
|
122
|
+
# v.transaction_open?(:first) # => true
|
123
|
+
# v.transaction_open?(:second) # => false
|
124
|
+
#
|
125
|
+
# v.gsub!(/you/, "world") # => "Hello, world."
|
126
|
+
# v.start_transaction(:second) # => ... (a Marshal string)
|
127
|
+
# v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
128
|
+
# v.transaction_name # => :second
|
129
|
+
# v.abort_transaction(:first) # => "Hello, you."
|
130
|
+
# v.transaction_open? # => false
|
131
|
+
#
|
132
|
+
# v.start_transaction(:first) # => ... (a Marshal string)
|
133
|
+
# v.gsub!(/you/, "world") # => "Hello, world."
|
134
|
+
# v.start_transaction(:second) # => ... (a Marshal string)
|
135
|
+
# v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
136
|
+
#
|
137
|
+
# v.commit_transaction(:first) # => "Hello, HAL."
|
138
|
+
# v.transaction_open? # => false
|
139
|
+
#
|
140
|
+
# == Block Usage
|
141
|
+
# include 'transaction/simple'
|
142
|
+
#
|
143
|
+
# v = "Hello, you." # => "Hello, you."
|
144
|
+
# Transaction::Simple.start(v) do |tv|
|
145
|
+
# # v has been extended with Transaction::Simple and an unnamed
|
146
|
+
# # transaction has been started.
|
147
|
+
# tv.transaction_open? # => true
|
148
|
+
# tv.gsub!(/you/, "world") # => "Hello, world."
|
149
|
+
#
|
150
|
+
# tv.rewind_transaction # => "Hello, you."
|
151
|
+
# tv.transaction_open? # => true
|
152
|
+
#
|
153
|
+
# tv.gsub!(/you/, "HAL") # => "Hello, HAL."
|
154
|
+
# # The following breaks out of the transaction block after
|
155
|
+
# # aborting the transaction.
|
156
|
+
# tv.abort_transaction # => "Hello, you."
|
157
|
+
# end
|
158
|
+
# # v still has Transaction::Simple applied from here on out.
|
159
|
+
# v.transaction_open? # => false
|
160
|
+
#
|
161
|
+
# Transaction::Simple.start(v) do |tv|
|
162
|
+
# tv.start_transaction # => ... (a Marshal string)
|
163
|
+
#
|
164
|
+
# tv.transaction_open? # => true
|
165
|
+
# tv.gsub!(/you/, "HAL") # => "Hello, HAL."
|
166
|
+
#
|
167
|
+
# # If #commit_transaction were called without having started a
|
168
|
+
# # second transaction, then it would break out of the transaction
|
169
|
+
# # block after committing the transaction.
|
170
|
+
# tv.commit_transaction # => "Hello, HAL."
|
171
|
+
# tv.transaction_open? # => true
|
172
|
+
# tv.abort_transaction # => "Hello, you."
|
173
|
+
# end
|
174
|
+
# v.transaction_open? # => false
|
175
|
+
#
|
176
|
+
# == Named Transaction Usage
|
177
|
+
# v = "Hello, you." # => "Hello, you."
|
178
|
+
# v.extend(Transaction::Simple) # => "Hello, you."
|
179
|
+
#
|
180
|
+
# v.start_transaction(:first) # => ... (a Marshal string)
|
181
|
+
# v.transaction_open? # => true
|
182
|
+
# v.transaction_open?(:first) # => true
|
183
|
+
# v.transaction_open?(:second) # => false
|
184
|
+
# v.gsub!(/you/, "world") # => "Hello, world."
|
185
|
+
#
|
186
|
+
# v.start_transaction(:second) # => ... (a Marshal string)
|
187
|
+
# v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
188
|
+
# v.rewind_transaction(:first) # => "Hello, you."
|
189
|
+
# v.transaction_open? # => true
|
190
|
+
# v.transaction_open?(:first) # => true
|
191
|
+
# v.transaction_open?(:second) # => false
|
192
|
+
#
|
193
|
+
# v.gsub!(/you/, "world") # => "Hello, world."
|
194
|
+
# v.start_transaction(:second) # => ... (a Marshal string)
|
195
|
+
# v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
196
|
+
# v.transaction_name # => :second
|
197
|
+
# v.abort_transaction(:first) # => "Hello, you."
|
198
|
+
# v.transaction_open? # => false
|
199
|
+
#
|
200
|
+
# v.start_transaction(:first) # => ... (a Marshal string)
|
201
|
+
# v.gsub!(/you/, "world") # => "Hello, world."
|
202
|
+
# v.start_transaction(:second) # => ... (a Marshal string)
|
203
|
+
# v.gsub!(/world/, "HAL") # => "Hello, HAL."
|
204
|
+
#
|
205
|
+
# v.commit_transaction(:first) # => "Hello, HAL."
|
206
|
+
# v.transaction_open? # => false
|
207
|
+
#
|
208
|
+
# == Contraindications
|
209
|
+
#
|
210
|
+
# While Transaction::Simple is very useful, it has some severe
|
211
|
+
# limitations that must be understood. Transaction::Simple:
|
212
|
+
#
|
213
|
+
# * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed
|
214
|
+
# cannot use Transaction::Simple.
|
215
|
+
# * does not manage resources. Resources external to the object and its
|
216
|
+
# instance variables are not managed at all. However, all instance
|
217
|
+
# variables and objects "belonging" to those instance variables are
|
218
|
+
# managed. If there are object reference counts to be handled,
|
219
|
+
# Transaction::Simple will probably cause problems.
|
220
|
+
# * is not inherently thread-safe. In the ACID ("atomic, consistent,
|
221
|
+
# isolated, durable") test, Transaction::Simple provides CD, but it is
|
222
|
+
# up to the user of Transaction::Simple to provide isolation and
|
223
|
+
# atomicity. Transactions should be considered "critical sections" in
|
224
|
+
# multi-threaded applications. If thread safety and atomicity is
|
225
|
+
# absolutely required, use Transaction::Simple::ThreadSafe, which uses
|
226
|
+
# a Mutex object to synchronize the accesses on the object during the
|
227
|
+
# transactional operations.
|
228
|
+
# * does not necessarily maintain Object#__id__ values on rewind or
|
229
|
+
# abort. This may change for future versions that will be Ruby 1.8 or
|
230
|
+
# better *only*. Certain objects that support #replace will maintain
|
231
|
+
# Object#__id__.
|
232
|
+
# * Can be a memory hog if you use many levels of transactions on many
|
233
|
+
# objects.
|
234
|
+
#
|
235
|
+
module Simple
|
236
|
+
VERSION = '1.2.0'
|
237
|
+
|
238
|
+
# Sets the Transaction::Simple debug object. It must respond to #<<.
|
239
|
+
# Sets the transaction debug object. Debugging will be performed
|
240
|
+
# automatically if there's a debug object. The generic transaction
|
241
|
+
# error class.
|
242
|
+
def self.debug_io=(io)
|
243
|
+
raise TransactionError, "Transaction Error: the transaction debug object must respond to #<<" unless io.respond_to?(:<<)
|
244
|
+
@tdi = io
|
245
|
+
end
|
246
|
+
|
247
|
+
# Returns the Transaction::Simple debug object. It must respond to
|
248
|
+
# #<<.
|
249
|
+
def self.debug_io
|
250
|
+
@tdi
|
251
|
+
end
|
252
|
+
|
253
|
+
# If +name+ is +nil+ (default), then returns +true+ if there is
|
254
|
+
# currently a transaction open.
|
255
|
+
#
|
256
|
+
# If +name+ is specified, then returns +true+ if there is currently a
|
257
|
+
# transaction that responds to +name+ open.
|
258
|
+
def transaction_open?(name = nil)
|
259
|
+
if name.nil?
|
260
|
+
Transaction::Simple.debug_io << "Transaction [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil?
|
261
|
+
return (not @__transaction_checkpoint__.nil?)
|
262
|
+
else
|
263
|
+
Transaction::Simple.debug_io << "Transaction(#{name.inspect}) [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil?
|
264
|
+
return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Returns the current name of the transaction. Transactions not
|
269
|
+
# explicitly named are named +nil+.
|
270
|
+
def transaction_name
|
271
|
+
raise TransactionError, "Transaction Error: No transaction open." if @__transaction_checkpoint__.nil?
|
272
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Transaction Name: #{@__transaction_names__[-1].inspect}\n" unless Transaction::Simple.debug_io.nil?
|
273
|
+
@__transaction_names__[-1]
|
274
|
+
end
|
275
|
+
|
276
|
+
# Starts a transaction. Stores the current object state. If a
|
277
|
+
# transaction name is specified, the transaction will be named.
|
278
|
+
# Transaction names must be unique. Transaction names of +nil+ will be
|
279
|
+
# treated as unnamed transactions.
|
280
|
+
def start_transaction(name = nil)
|
281
|
+
@__transaction_level__ ||= 0
|
282
|
+
@__transaction_names__ ||= []
|
283
|
+
|
284
|
+
if name.nil?
|
285
|
+
@__transaction_names__ << nil
|
286
|
+
ss = ""
|
287
|
+
else
|
288
|
+
raise TransactionError, "Transaction Error: Named transactions must be unique." if @__transaction_names__.include?(name)
|
289
|
+
@__transaction_names__ << name
|
290
|
+
ss = "(#{name.inspect})"
|
291
|
+
end
|
292
|
+
|
293
|
+
@__transaction_level__ += 1
|
294
|
+
|
295
|
+
Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} Start Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
|
296
|
+
|
297
|
+
@__transaction_checkpoint__ = Marshal.dump(self)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Rewinds the transaction. If +name+ is specified, then the
|
301
|
+
# intervening transactions will be aborted and the named transaction
|
302
|
+
# will be rewound. Otherwise, only the current transaction is rewound.
|
303
|
+
def rewind_transaction(name = nil)
|
304
|
+
raise TransactionError, "Transaction Error: Cannot rewind. There is no current transaction." if @__transaction_checkpoint__.nil?
|
305
|
+
if name.nil?
|
306
|
+
__rewind_this_transaction
|
307
|
+
ss = ""
|
308
|
+
else
|
309
|
+
raise TransactionError, "Transaction Error: Cannot rewind to transaction #{name.inspect} because it does not exist." unless @__transaction_names__.include?(name)
|
310
|
+
ss = "(#{name})"
|
311
|
+
|
312
|
+
while @__transaction_names__[-1] != name
|
313
|
+
@__transaction_checkpoint__ = __rewind_this_transaction
|
314
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
|
315
|
+
@__transaction_level__ -= 1
|
316
|
+
@__transaction_names__.pop
|
317
|
+
end
|
318
|
+
__rewind_this_transaction
|
319
|
+
end
|
320
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
|
321
|
+
self
|
322
|
+
end
|
323
|
+
|
324
|
+
# Aborts the transaction. Resets the object state to what it was
|
325
|
+
# before the transaction was started and closes the transaction. If
|
326
|
+
# +name+ is specified, then the intervening transactions and the named
|
327
|
+
# transaction will be aborted. Otherwise, only the current transaction
|
328
|
+
# is aborted.
|
329
|
+
#
|
330
|
+
# If the current or named transaction has been started by a block
|
331
|
+
# (Transaction::Simple.start), then the execution of the block will be
|
332
|
+
# halted with +break+ +self+.
|
333
|
+
def abort_transaction(name = nil)
|
334
|
+
raise TransactionError, "Transaction Error: Cannot abort. There is no current transaction." if @__transaction_checkpoint__.nil?
|
335
|
+
|
336
|
+
# Check to see if we are trying to abort a transaction that is
|
337
|
+
# outside of the current transaction block. Otherwise, raise
|
338
|
+
# TransactionAborted if they are the same.
|
339
|
+
if @__transaction_block__ and name
|
340
|
+
nix = @__transaction_names__.index(name) + 1
|
341
|
+
raise TransactionError, "Transaction Error: Cannot abort a transaction outside of this execution block." if nix < @__transaction_block__
|
342
|
+
|
343
|
+
raise TransactionAborted if @__transaction_block__ == nix
|
344
|
+
end
|
345
|
+
|
346
|
+
raise TransactionAborted if @__transaction_block__ == @__transaction_level__
|
347
|
+
|
348
|
+
if name.nil?
|
349
|
+
__abort_transaction(name)
|
350
|
+
else
|
351
|
+
raise TransactionError, "Transaction Error: Cannot abort nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name)
|
352
|
+
|
353
|
+
__abort_transaction(name) while @__transaction_names__.include?(name)
|
354
|
+
end
|
355
|
+
self
|
356
|
+
end
|
357
|
+
|
358
|
+
# If +name+ is +nil+ (default), the current transaction level is
|
359
|
+
# closed out and the changes are committed.
|
360
|
+
#
|
361
|
+
# If +name+ is specified and +name+ is in the list of named
|
362
|
+
# transactions, then all transactions are closed and committed until
|
363
|
+
# the named transaction is reached.
|
364
|
+
def commit_transaction(name = nil)
|
365
|
+
raise TransactionError, "Transaction Error: Cannot commit. There is no current transaction." if @__transaction_checkpoint__.nil?
|
366
|
+
|
367
|
+
# Check to see if we are trying to commit a transaction that is
|
368
|
+
# outside of the current transaction block. Otherwise, raise
|
369
|
+
# TransactionCommitted if they are the same.
|
370
|
+
if @__transaction_block__ and name
|
371
|
+
nix = @__transaction_names__.index(name) + 1
|
372
|
+
raise TransactionError, "Transaction Error: Cannot commit a transaction outside of this execution block." if nix < @__transaction_block__
|
373
|
+
|
374
|
+
raise TransactionCommitted if @__transaction_block__ == nix
|
375
|
+
end
|
376
|
+
|
377
|
+
raise TransactionCommitted if @__transaction_block__ == @__transaction_level__
|
378
|
+
|
379
|
+
if name.nil?
|
380
|
+
ss = ""
|
381
|
+
__commit_transaction
|
382
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
|
383
|
+
else
|
384
|
+
raise TransactionError, "Transaction Error: Cannot commit nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name)
|
385
|
+
ss = "(#{name})"
|
386
|
+
|
387
|
+
while @__transaction_names__[-1] != name
|
388
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
|
389
|
+
__commit_transaction
|
390
|
+
end
|
391
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
|
392
|
+
__commit_transaction
|
393
|
+
end
|
394
|
+
|
395
|
+
self
|
396
|
+
end
|
397
|
+
|
398
|
+
# Alternative method for calling the transaction methods. An optional
|
399
|
+
# name can be specified for named transaction support.
|
400
|
+
#
|
401
|
+
# #transaction(:start):: #start_transaction
|
402
|
+
# #transaction(:rewind):: #rewind_transaction
|
403
|
+
# #transaction(:abort):: #abort_transaction
|
404
|
+
# #transaction(:commit):: #commit_transaction
|
405
|
+
# #transaction(:name):: #transaction_name
|
406
|
+
# #transaction:: #transaction_open?
|
407
|
+
def transaction(action = nil, name = nil)
|
408
|
+
case action
|
409
|
+
when :start
|
410
|
+
start_transaction(name)
|
411
|
+
when :rewind
|
412
|
+
rewind_transaction(name)
|
413
|
+
when :abort
|
414
|
+
abort_transaction(name)
|
415
|
+
when :commit
|
416
|
+
commit_transaction(name)
|
417
|
+
when :name
|
418
|
+
transaction_name
|
419
|
+
when nil
|
420
|
+
transaction_open?(name)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
class << self
|
425
|
+
def __common_start(name, vars, &block)
|
426
|
+
raise TransactionError, "Transaction Error: Cannot start a transaction with no objects." if vars.empty?
|
427
|
+
|
428
|
+
if block
|
429
|
+
begin
|
430
|
+
vlevel = {}
|
431
|
+
|
432
|
+
vars.each do |vv|
|
433
|
+
vv.extend(Transaction::Simple)
|
434
|
+
vv.start_transaction(name)
|
435
|
+
vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
|
436
|
+
vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
|
437
|
+
end
|
438
|
+
|
439
|
+
yield *vars
|
440
|
+
rescue TransactionAborted
|
441
|
+
vars.each do |vv|
|
442
|
+
if name.nil? and vv.transaction_open?
|
443
|
+
loop do
|
444
|
+
tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
|
445
|
+
vv.instance_variable_set(:@__transaction_block__, -1)
|
446
|
+
break if tlevel < vlevel[vv.__id__]
|
447
|
+
vv.abort_transaction if vv.transaction_open?
|
448
|
+
end
|
449
|
+
elsif vv.transaction_open?(name)
|
450
|
+
vv.instance_variable_set(:@__transaction_block__, -1)
|
451
|
+
vv.abort_transaction(name)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
rescue TransactionCommitted
|
455
|
+
nil
|
456
|
+
ensure
|
457
|
+
vars.each do |vv|
|
458
|
+
if name.nil? and vv.transaction_open?
|
459
|
+
loop do
|
460
|
+
tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
|
461
|
+
break if tlevel < vlevel[vv.__id__]
|
462
|
+
vv.instance_variable_set(:@__transaction_block__, -1)
|
463
|
+
vv.commit_transaction if vv.transaction_open?
|
464
|
+
end
|
465
|
+
elsif vv.transaction_open?(name)
|
466
|
+
vv.instance_variable_set(:@__transaction_block__, -1)
|
467
|
+
vv.commit_transaction(name)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
else
|
472
|
+
vars.each do |vv|
|
473
|
+
vv.extend(Transaction::Simple)
|
474
|
+
vv.start_transaction(name)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
private :__common_start
|
479
|
+
|
480
|
+
def start_named(name, *vars, &block)
|
481
|
+
__common_start(name, vars, &block)
|
482
|
+
end
|
483
|
+
|
484
|
+
def start(*vars, &block)
|
485
|
+
__common_start(nil, vars, &block)
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
def __abort_transaction(name = nil) #:nodoc:
|
490
|
+
@__transaction_checkpoint__ = __rewind_this_transaction
|
491
|
+
|
492
|
+
if name.nil?
|
493
|
+
ss = ""
|
494
|
+
else
|
495
|
+
ss = "(#{name.inspect})"
|
496
|
+
end
|
497
|
+
|
498
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Abort Transaction#{ss}\n" unless Transaction::Simple.debug_io.nil?
|
499
|
+
@__transaction_level__ -= 1
|
500
|
+
@__transaction_names__.pop
|
501
|
+
if @__transaction_level__ < 1
|
502
|
+
@__transaction_level__ = 0
|
503
|
+
@__transaction_names__ = []
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc:
|
508
|
+
SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
|
509
|
+
|
510
|
+
def __rewind_this_transaction #:nodoc:
|
511
|
+
r = Marshal.restore(@__transaction_checkpoint__)
|
512
|
+
|
513
|
+
begin
|
514
|
+
self.replace(r) if respond_to?(:replace)
|
515
|
+
rescue
|
516
|
+
nil
|
517
|
+
end
|
518
|
+
|
519
|
+
r.instance_variables.each do |i|
|
520
|
+
next if SKIP_TRANSACTION_VARS.include?(i)
|
521
|
+
if respond_to?(:instance_variable_get)
|
522
|
+
instance_variable_set(i, r.instance_variable_get(i))
|
523
|
+
else
|
524
|
+
instance_eval(%q|#{i} = r.instance_eval("#{i}")|)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
if respond_to?(:instance_variable_get)
|
529
|
+
return r.instance_variable_get(TRANSACTION_CHECKPOINT)
|
530
|
+
else
|
531
|
+
return r.instance_eval(TRANSACTION_CHECKPOINT)
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
def __commit_transaction #:nodoc:
|
536
|
+
if respond_to?(:instance_variable_get)
|
537
|
+
@__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
|
538
|
+
else
|
539
|
+
@__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
|
540
|
+
end
|
541
|
+
|
542
|
+
@__transaction_level__ -= 1
|
543
|
+
@__transaction_names__.pop
|
544
|
+
if @__transaction_level__ < 1
|
545
|
+
@__transaction_level__ = 0
|
546
|
+
@__transaction_names__ = []
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
private :__abort_transaction, :__rewind_this_transaction, :__commit_transaction
|
551
|
+
|
552
|
+
# = Transaction::Simple::ThreadSafe
|
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
|
590
|
+
end
|
591
|
+
end
|