transaction-simple 1.4.0 → 1.4.0.2
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/.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
|