transaction-simple 1.4.0 → 1.4.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/{History.txt → History.rdoc} +16 -19
- data/Licence.rdoc +23 -0
- data/Manifest.txt +7 -6
- data/{Readme.txt → README.rdoc} +55 -40
- data/Rakefile +14 -112
- data/lib/transaction-simple.rb +5 -0
- data/lib/transaction/simple.rb +110 -77
- data/lib/transaction/simple/group.rb +82 -92
- data/lib/transaction/simple/threadsafe.rb +25 -35
- data/lib/transaction/simple/threadsafe/group.rb +9 -19
- data/research/instance_variable_defined.rb +22 -0
- data/research/special-dumpable-string.rb +42 -0
- data/research/special-dumpable.rb +130 -0
- data/test/test_broken_graph.rb +4 -14
- data/test/test_transaction_simple.rb +4 -14
- data/test/test_transaction_simple_group.rb +4 -14
- data/test/test_transaction_simple_threadsafe.rb +4 -14
- metadata +174 -58
- data/Install.txt +0 -21
- data/Licence.txt +0 -25
- data/setup.rb +0 -1585
- data/test/test_all.rb +0 -25
@@ -1,61 +1,49 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
#
|
1
|
+
# -*- ruby encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'transaction/simple'
|
4
|
+
|
5
|
+
# A transaction group is an object wrapper that manages a group of objects
|
6
|
+
# as if they were a single object for the purpose of transaction management.
|
7
|
+
# All transactions for this group of objects should be performed against the
|
8
|
+
# transaction group object, not against individual objects in the group.
|
6
9
|
#
|
7
|
-
#
|
8
|
-
#
|
10
|
+
# == Transaction Group Usage
|
11
|
+
# require 'transaction/simple/group'
|
9
12
|
#
|
10
|
-
#
|
13
|
+
# x = "Hello, you."
|
14
|
+
# y = "And you, too."
|
11
15
|
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# x.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
39
|
-
# y.gsub!(/me/, "Dave") # -> "And Dave, too."
|
40
|
-
# g.rewind_transaction(:second) # -> [ x, y ]
|
41
|
-
# x # -> "Hello, world."
|
42
|
-
# y # -> "And me, too."
|
43
|
-
#
|
44
|
-
# x.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
45
|
-
# y.gsub!(/me/, "Dave") # -> "And Dave, too."
|
46
|
-
#
|
47
|
-
# g.commit_transaction(:second) # -> [ x, y ]
|
48
|
-
# x # -> "Hello, HAL."
|
49
|
-
# y # -> "And Dave, too."
|
50
|
-
#
|
51
|
-
# g.abort_transaction(:first) # -> [ x, y ]
|
52
|
-
# x = -> "Hello, you."
|
53
|
-
# y = -> "And you, too."
|
16
|
+
# g = Transaction::Simple::Group.new(x, y)
|
17
|
+
# g.start_transaction(:first) # -> [ x, y ]
|
18
|
+
# g.transaction_open?(:first) # -> true
|
19
|
+
# x.transaction_open?(:first) # -> true
|
20
|
+
# y.transaction_open?(:first) # -> true
|
21
|
+
#
|
22
|
+
# x.gsub!(/you/, "world") # -> "Hello, world."
|
23
|
+
# y.gsub!(/you/, "me") # -> "And me, too."
|
24
|
+
#
|
25
|
+
# g.start_transaction(:second) # -> [ x, y ]
|
26
|
+
# x.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
27
|
+
# y.gsub!(/me/, "Dave") # -> "And Dave, too."
|
28
|
+
# g.rewind_transaction(:second) # -> [ x, y ]
|
29
|
+
# x # -> "Hello, world."
|
30
|
+
# y # -> "And me, too."
|
31
|
+
#
|
32
|
+
# x.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
33
|
+
# y.gsub!(/me/, "Dave") # -> "And Dave, too."
|
34
|
+
#
|
35
|
+
# g.commit_transaction(:second) # -> [ x, y ]
|
36
|
+
# x # -> "Hello, HAL."
|
37
|
+
# y # -> "And Dave, too."
|
38
|
+
#
|
39
|
+
# g.abort_transaction(:first) # -> [ x, y ]
|
40
|
+
# x = -> "Hello, you."
|
41
|
+
# y = -> "And you, too."
|
54
42
|
class Transaction::Simple::Group
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
43
|
+
# Creates a transaction group for the provided objects. If a block is
|
44
|
+
# provided, the transaction group object is yielded to the block; when the
|
45
|
+
# block is finished, the transaction group object will be cleared with
|
46
|
+
# #clear.
|
59
47
|
def initialize(*objects)
|
60
48
|
@objects = objects || []
|
61
49
|
@objects.freeze
|
@@ -70,77 +58,79 @@ class Transaction::Simple::Group
|
|
70
58
|
end
|
71
59
|
end
|
72
60
|
|
73
|
-
|
61
|
+
# Returns the objects that are covered by this transaction group.
|
74
62
|
attr_reader :objects
|
75
63
|
|
76
|
-
|
77
|
-
|
64
|
+
# Clears the object group. Removes references to the objects so that they
|
65
|
+
# can be garbage collected.
|
78
66
|
def clear
|
79
67
|
@objects = @objects.dup.clear
|
80
68
|
end
|
81
69
|
|
82
|
-
|
83
|
-
|
84
|
-
|
70
|
+
# Tests to see if all of the objects in the group have an open
|
71
|
+
# transaction. See Transaction::Simple#transaction_open? for more
|
72
|
+
# information.
|
85
73
|
def transaction_open?(name = nil)
|
86
74
|
@objects.inject(true) do |val, obj|
|
87
75
|
val = val and obj.transaction_open?(name)
|
88
76
|
end
|
89
77
|
end
|
90
78
|
|
91
|
-
|
92
|
-
|
79
|
+
# Returns the current name of the transaction for the group. Transactions
|
80
|
+
# not explicitly named are named +nil+.
|
93
81
|
def transaction_name
|
94
82
|
@objects[0].transaction_name
|
95
83
|
end
|
96
84
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
85
|
+
# Starts a transaction for the group. Stores the current object state. If
|
86
|
+
# a transaction name is specified, the transaction will be named.
|
87
|
+
# Transaction names must be unique. Transaction names of +nil+ will be
|
88
|
+
# treated as unnamed transactions.
|
101
89
|
def start_transaction(name = nil)
|
102
90
|
@objects.each { |obj| obj.start_transaction(name) }
|
103
91
|
end
|
104
92
|
|
105
|
-
|
106
|
-
|
107
|
-
|
93
|
+
# Rewinds the transaction. If +name+ is specified, then the intervening
|
94
|
+
# transactions will be aborted and the named transaction will be rewound.
|
95
|
+
# Otherwise, only the current transaction is rewound.
|
108
96
|
def rewind_transaction(name = nil)
|
109
97
|
@objects.each { |obj| obj.rewind_transaction(name) }
|
110
98
|
end
|
111
99
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
100
|
+
# Aborts the transaction. Resets the object state to what it was before
|
101
|
+
# the transaction was started and closes the transaction. If +name+ is
|
102
|
+
# specified, then the intervening transactions and the named transaction
|
103
|
+
# will be aborted. Otherwise, only the current transaction is aborted.
|
104
|
+
#
|
105
|
+
# If the current or named transaction has been started by a block
|
106
|
+
# (Transaction::Simple.start), then the execution of the block will be
|
107
|
+
# halted with +break+ +self+.
|
120
108
|
def abort_transaction(name = nil)
|
121
109
|
@objects.each { |obj| obj.abort_transaction(name) }
|
122
110
|
end
|
123
111
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
112
|
+
# If +name+ is +nil+ (default), the current transaction level is closed
|
113
|
+
# out and the changes are committed.
|
114
|
+
#
|
115
|
+
# If +name+ is specified and +name+ is in the list of named transactions,
|
116
|
+
# then all transactions are closed and committed until the named
|
117
|
+
# transaction is reached.
|
130
118
|
def commit_transaction(name = nil)
|
131
119
|
@objects.each { |obj| obj.commit_transaction(name) }
|
132
120
|
end
|
133
121
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
122
|
+
# Alternative method for calling the transaction methods. An optional name
|
123
|
+
# can be specified for named transaction support.
|
124
|
+
#
|
125
|
+
# #transaction(:start):: #start_transaction
|
126
|
+
# #transaction(:rewind):: #rewind_transaction
|
127
|
+
# #transaction(:abort):: #abort_transaction
|
128
|
+
# #transaction(:commit):: #commit_transaction
|
129
|
+
# #transaction(:name):: #transaction_name
|
130
|
+
# #transaction:: #transaction_open?
|
143
131
|
def transaction(action = nil, name = nil)
|
144
132
|
@objects.each { |obj| obj.transaction(action, name) }
|
145
133
|
end
|
146
134
|
end
|
135
|
+
|
136
|
+
# vim: syntax=ruby
|
@@ -1,16 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Simple object transaction support for Ruby
|
4
|
-
# http://rubyforge.org/projects/trans-simple/
|
5
|
-
# Version 1.4.0
|
6
|
-
#
|
7
|
-
# Licensed under a MIT-style licence. See Licence.txt in the main
|
8
|
-
# distribution for full licensing information.
|
9
|
-
#
|
10
|
-
# Copyright (c) 2003 - 2007 Austin Ziegler
|
11
|
-
#
|
12
|
-
# $Id: threadsafe.rb 50 2007-02-03 20:26:19Z austin $
|
13
|
-
#++
|
1
|
+
# -*- ruby encoding: utf-8 -*-
|
2
|
+
|
14
3
|
require 'transaction/simple'
|
15
4
|
require 'thread'
|
16
5
|
|
@@ -20,28 +9,27 @@ module Transaction
|
|
20
9
|
class TransactionThreadError < TransactionError; end
|
21
10
|
end
|
22
11
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# y.extend(Transaction::Simple) # Not threadsafe
|
12
|
+
# Thread-safe simple object transaction support for Ruby.
|
13
|
+
# Transaction::Simple::ThreadSafe is used in the same way as
|
14
|
+
# Transaction::Simple. Transaction::Simple::ThreadSafe uses a Mutex object
|
15
|
+
# to ensure atomicity at the cost of performance in threaded applications.
|
16
|
+
#
|
17
|
+
# Transaction::Simple::ThreadSafe will not wait to obtain a lock; if the
|
18
|
+
# lock cannot be obtained immediately, a Transaction::TransactionThreadError
|
19
|
+
# will be raised.
|
20
|
+
#
|
21
|
+
# Thanks to Mauricio Fernandez for help with getting this part working.
|
22
|
+
#
|
23
|
+
# Threadsafe transactions can be used in any place that normal transactions
|
24
|
+
# would. The main difference would be in setup:
|
25
|
+
#
|
26
|
+
# require 'transaction/simple/threadsafe'
|
27
|
+
#
|
28
|
+
# x = "Hello, you."
|
29
|
+
# x.extend(Transaction::Simple::ThreadSafe) # Threadsafe
|
30
|
+
#
|
31
|
+
# y = "Hello, you."
|
32
|
+
# y.extend(Transaction::Simple) # Not threadsafe
|
45
33
|
module Transaction::Simple::ThreadSafe
|
46
34
|
include Transaction::Simple
|
47
35
|
|
@@ -66,3 +54,5 @@ module Transaction::Simple::ThreadSafe
|
|
66
54
|
EOS
|
67
55
|
end
|
68
56
|
end
|
57
|
+
|
58
|
+
# vim: syntax=ruby
|
@@ -1,24 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Simple object transaction support for Ruby
|
4
|
-
# http://rubyforge.org/projects/trans-simple/
|
5
|
-
# Version 1.4.0
|
6
|
-
#
|
7
|
-
# Licensed under a MIT-style licence. See Licence.txt in the main
|
8
|
-
# distribution for full licensing information.
|
9
|
-
#
|
10
|
-
# Copyright (c) 2003 - 2007 Austin Ziegler
|
11
|
-
#
|
12
|
-
# $Id: group.rb 47 2007-02-03 15:02:51Z austin $
|
13
|
-
#++
|
1
|
+
# -*- ruby encoding: utf-8 -*-
|
2
|
+
|
14
3
|
require 'transaction/simple/threadsafe'
|
15
4
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# group.
|
5
|
+
# A transaction group is an object wrapper that manages a group of objects
|
6
|
+
# as if they were a single object for the purpose of transaction management.
|
7
|
+
# All transactions for this group of objects should be performed against the
|
8
|
+
# transaction group object, not against individual objects in the group.
|
9
|
+
# This is the threadsafe version of a transaction group.
|
22
10
|
class Transaction::Simple::ThreadSafe::Group < Transaction::Simple::Group
|
23
11
|
def initialize(*objects)
|
24
12
|
@objects = objects || []
|
@@ -34,3 +22,5 @@ class Transaction::Simple::ThreadSafe::Group < Transaction::Simple::Group
|
|
34
22
|
end
|
35
23
|
end
|
36
24
|
end
|
25
|
+
|
26
|
+
# vim: syntax=ruby
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- ruby encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Provided by Nobu Nakada.
|
4
|
+
# You can use this code for previous versions.
|
5
|
+
|
6
|
+
unless defined?(instance_variable_defined?)
|
7
|
+
module Kernel
|
8
|
+
(t = Object.new).instance_eval { @instance_variable = 1 }
|
9
|
+
case t.instance_variables[0]
|
10
|
+
when Symbol
|
11
|
+
def instance_variable_defined?(var)
|
12
|
+
instance_variables.include?(var.to_sym)
|
13
|
+
end
|
14
|
+
when String
|
15
|
+
def instance_variable_defined?(var)
|
16
|
+
instance_variables.include?(var.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# vim: syntax=ruby
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- ruby encoding: utf-8 -*-
|
2
|
+
|
3
|
+
load 'special-dumpable.rb'
|
4
|
+
|
5
|
+
class Child
|
6
|
+
attr_accessor :parent
|
7
|
+
end
|
8
|
+
|
9
|
+
class Parent < String
|
10
|
+
include SpecialDumpable
|
11
|
+
|
12
|
+
attr_reader :children
|
13
|
+
def initialize(value)
|
14
|
+
super
|
15
|
+
@children = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def << child
|
19
|
+
child.parent = self
|
20
|
+
@children << child
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
parent = Parent.new("gold")
|
25
|
+
puts "parent(#{parent}).object_id: #{parent.object_id}"
|
26
|
+
parent << Child.new
|
27
|
+
puts "parent(#{parent}).children[0].parent.object_id: #{parent.children[0].parent.object_id}"
|
28
|
+
puts "starting transaction with childcount #{parent.children.size}"
|
29
|
+
s = parent.special_dump
|
30
|
+
parent << Child.new
|
31
|
+
parent.gsub!(/gold/, 'pyrite')
|
32
|
+
puts "parent(#{parent}).children[1].parent.object_id: #{parent.children[1].parent.object_id}"
|
33
|
+
puts "aborting transaction with childcount #{parent.children.size}"
|
34
|
+
parent.special_restore s
|
35
|
+
puts "parent(#{parent})"
|
36
|
+
puts "aborted transaction with childcount #{parent.children.size}"
|
37
|
+
puts "parent.object_id: #{parent.object_id}"
|
38
|
+
puts "parent.children[0].parent.object_id: #{parent.children[0].parent.object_id}"
|
39
|
+
parent << Child.new
|
40
|
+
puts "parent.children[1].parent.object_id: #{parent.children[1].parent.object_id}"
|
41
|
+
|
42
|
+
# vim: syntax=ruby
|
@@ -0,0 +1,130 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- ruby encoding: utf-8 -*-
|
3
|
+
#
|
4
|
+
# SpecialDumpable - workaround for a problem in Transaction::Simple
|
5
|
+
#
|
6
|
+
# (C) 2006 Pit Capitain
|
7
|
+
#
|
8
|
+
# For a description of the problem see http://www.halostatue.ca/2006/10/22/
|
9
|
+
# ruby-conference-2006-day-1-evening-friday-20-october-2006/
|
10
|
+
# and http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155092
|
11
|
+
#
|
12
|
+
# The workaround:
|
13
|
+
#
|
14
|
+
# The objects that are restored from Marshal shouldn't reference the newly
|
15
|
+
# created root object, but the original root object. After studying the
|
16
|
+
# source code of Marshal, I found that this could be achieved with a
|
17
|
+
# special _load method of the root object's class which returns the
|
18
|
+
# original root object. Then the original root object would be stored in
|
19
|
+
# the internal list of restored objects and references to the root object
|
20
|
+
# would use the original one.
|
21
|
+
#
|
22
|
+
# See SpecialDumpable::ClassMethods#_load
|
23
|
+
#
|
24
|
+
# In order to get at the original root object from a class method, we store
|
25
|
+
# its object_id in the Marshal string.
|
26
|
+
#
|
27
|
+
# See SpecialDumpable#_dump
|
28
|
+
#
|
29
|
+
# Note: this means that classes with their own _load and _dump methods
|
30
|
+
# cannot be used with this implementation. I think it should be possible to
|
31
|
+
# enhance the implementation to support these classes, too.
|
32
|
+
#
|
33
|
+
# Using those two methods, the objects restored from Marshal really
|
34
|
+
# reference the original root object. But this is not enough. We have to
|
35
|
+
# restore the instance variables of the root object, too. The _dump method
|
36
|
+
# from above only dumps the object_id of the root object, not its instance
|
37
|
+
# variables.
|
38
|
+
#
|
39
|
+
# For the instance variables, we create a new object and store the instance
|
40
|
+
# variables of the root object there. Then we not only serialize the root
|
41
|
+
# object, but an array with both the root object and the object with the
|
42
|
+
# instance variables.
|
43
|
+
#
|
44
|
+
# See SpecialDumpable#special_dump
|
45
|
+
#
|
46
|
+
# The root object dumps its object_id, and the object with the instance
|
47
|
+
# variables dumps the instance variables of the root object.
|
48
|
+
#
|
49
|
+
# When restoring the objects, Marshal creates an array with the root object
|
50
|
+
# plus an object with the restored instance variables of the root object.
|
51
|
+
# We only need to replace the current instance variables of the root object
|
52
|
+
# with the restored instance variables to get the desired behaviour.
|
53
|
+
#
|
54
|
+
# See SpecialDumpable#special_restore
|
55
|
+
#
|
56
|
+
# That's it.
|
57
|
+
|
58
|
+
module SpecialDumpable
|
59
|
+
def self.included base
|
60
|
+
base.extend ClassMethods
|
61
|
+
end
|
62
|
+
|
63
|
+
module ClassMethods
|
64
|
+
def _load source
|
65
|
+
ObjectSpace._id2ref source.to_i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def _dump limit
|
70
|
+
object_id.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def special_dump
|
74
|
+
value_holder = Object.new
|
75
|
+
SpecialDumpable.copy_instance_variables self, value_holder
|
76
|
+
Marshal.dump [ self, value_holder ]
|
77
|
+
end
|
78
|
+
|
79
|
+
def special_restore source
|
80
|
+
self_that_can_be_ignored, value_holder = Marshal.restore source
|
81
|
+
SpecialDumpable.copy_instance_variables value_holder, self
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.copy_instance_variables from, to
|
86
|
+
from.instance_variables.each do |var|
|
87
|
+
val = from.instance_variable_get var
|
88
|
+
to.instance_variable_set var, val
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
if __FILE__ == $0
|
95
|
+
class Child
|
96
|
+
attr_accessor :parent
|
97
|
+
end
|
98
|
+
|
99
|
+
class Parent
|
100
|
+
include SpecialDumpable
|
101
|
+
|
102
|
+
attr_reader :children
|
103
|
+
def initialize
|
104
|
+
@children = []
|
105
|
+
end
|
106
|
+
|
107
|
+
def << child
|
108
|
+
child.parent = self
|
109
|
+
@children << child
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
parent = Parent.new
|
114
|
+
puts "parent.object_id: #{parent.object_id}"
|
115
|
+
parent << Child.new
|
116
|
+
puts "parent.children[0].parent.object_id: #{parent.children[0].parent.object_id}"
|
117
|
+
puts "starting transaction with childcount #{parent.children.size}"
|
118
|
+
s = parent.special_dump
|
119
|
+
parent << Child.new
|
120
|
+
puts "parent.children[1].parent.object_id: #{parent.children[1].parent.object_id}"
|
121
|
+
puts "aborting transaction with childcount #{parent.children.size}"
|
122
|
+
parent.special_restore s
|
123
|
+
puts "aborted transaction with childcount #{parent.children.size}"
|
124
|
+
puts "parent.object_id: #{parent.object_id}"
|
125
|
+
puts "parent.children[0].parent.object_id: #{parent.children[0].parent.object_id}"
|
126
|
+
parent << Child.new
|
127
|
+
puts "parent.children[1].parent.object_id: #{parent.children[1].parent.object_id}"
|
128
|
+
end
|
129
|
+
|
130
|
+
# vim: syntax=ruby
|