[ANN] Transaction::Simple 1.2.0

A

Austin Ziegler

Transaction::Simple for Ruby
Simple object transaction support for Ruby

Introduction
------------
Transaction::Simple provides a generic way to add active transactional support
to objects. The transaction methods added by this module will work with most
objects, excluding those that cannot be Marshal-ed (bindings, procedure
objects, IO instances, or singleton objects).

The transactions supported by Transaction::Simple are not backend transaction;
that is, they have nothing to do with any sort of data store. They are "live"
transactions occurring in memory and in the object itself. This is to allow
"test" changes to be made to an object before making the changes permanent.

Transaction::Simple can handle an "infinite" number of transactional levels
(limited only by memory). If I open two transactions, commit the first, but
abort the second, the object will revert to the original version.

Transaction::Simple supports "named" transactions, so that multiple levels of
transactions can be committed, aborted, or rewound by referring to the
appropriate name of the transaction. Names may be any object except nil.

Copyright: Copyright (c) 2003-2004 by Austin Ziegler
Version: 1.2.0
Licence: MIT-Style

Thanks to David Black and Mauricio Fern?ndez for their help with this library.

Usage
-----
include 'transaction/simple'

v = "Hello, you." # => "Hello, you."
v.extend(Transaction::Simple) # => "Hello, you."

v.start_transaction # => ... (a Marshal string)
v.transaction_open? # => true
v.gsub!(/you/, "world") # => "Hello, world."

v.rewind_transaction # => "Hello, you."
v.transaction_open? # => true

v.gsub!(/you/, "HAL") # => "Hello, HAL."
v.abort_transaction # => "Hello, you."
v.transaction_open? # => false

v.start_transaction # => ... (a Marshal string)
v.start_transaction # => ... (a Marshal string)

v.transaction_open? # => true
v.gsub!(/you/, "HAL") # => "Hello, HAL."

v.commit_transaction # => "Hello, HAL."
v.transaction_open? # => true
v.abort_transaction # => "Hello, you."
v.transaction_open? # => false

Named Transaction Usage
-----------------------
v = "Hello, you." # => "Hello, you."
v.extend(Transaction::Simple) # => "Hello, you."

v.start_transaction:)first) # => ... (a Marshal string)
v.transaction_open? # => true
v.transaction_open?:)first) # => true
v.transaction_open?:)second) # => false
v.gsub!(/you/, "world") # => "Hello, world."

v.start_transaction:)second) # => ... (a Marshal string)
v.gsub!(/world/, "HAL") # => "Hello, HAL."
v.rewind_transaction:)first) # => "Hello, you."
v.transaction_open? # => true
v.transaction_open?:)first) # => true
v.transaction_open?:)second) # => false

v.gsub!(/you/, "world") # => "Hello, world."
v.start_transaction:)second) # => ... (a Marshal string)
v.gsub!(/world/, "HAL") # => "Hello, HAL."
v.transaction_name # => :second
v.abort_transaction:)first) # => "Hello, you."
v.transaction_open? # => false

v.start_transaction:)first) # => ... (a Marshal string)
v.gsub!(/you/, "world") # => "Hello, world."
v.start_transaction:)second) # => ... (a Marshal string)
v.gsub!(/world/, "HAL") # => "Hello, HAL."

v.commit_transaction:)first) # => "Hello, HAL."
v.transaction_open? # => false

Contraindications
-----------------
While Transaction::Simple is very useful, it has some severe limitations that
must be understood. Transaction::Simple:

* uses Marshal. Thus, any object which cannot be Marshal-ed cannot use
Transaction::Simple.
* does not manage resources. Resources external to the object and its instance
variables are not managed at all. However, all instance variables and
objects "belonging" to those instance variables are managed. If there are
object reference counts to be handled, Transaction::Simple will probably
cause problems.
* is not thread-safe. In the ACID ("atomic, consistent, isolated, durable")
test, Transaction::Simple provides C and D, but it is up to the user of
Transaction::Simple to provide isolation. Transactions should be considered
"critical sections" in multi-threaded applications. Thread safety can be
ensured with Transaction::Simple::ThreadSafe.
* does not maintain Object#__id__ values on rewind or abort. This may change
for future versions that will be Ruby 1.8 or better only.

This can be found on RubyForge:
http://rubyforge.org/frs/?group_id=295&release_id=1025

The new gem will be available from the usual location within the hour.

-austin
 
M

Michael Neumann

Contraindications
-----------------
While Transaction::Simple is very useful, it has some severe limitations that
must be understood. Transaction::Simple:

* uses Marshal. Thus, any object which cannot be Marshal-ed cannot use
Transaction::Simple.
* does not manage resources. Resources external to the object and its instance
variables are not managed at all. However, all instance variables and
objects "belonging" to those instance variables are managed. If there are
object reference counts to be handled, Transaction::Simple will probably
cause problems.
* is not thread-safe. In the ACID ("atomic, consistent, isolated, durable")
test, Transaction::Simple provides C and D, but it is up to the user of
Transaction::Simple to provide isolation. Transactions should be considered
"critical sections" in multi-threaded applications. Thread safety can be
ensured with Transaction::Simple::ThreadSafe.
* does not maintain Object#__id__ values on rewind or abort. This may change
for future versions that will be Ruby 1.8 or better only.

Hm, I've written a similar library for Wee, which saves snapshots of
objects, and is able to restore the old object from a snapshot. It does
not use Marshal, and maintains __id__ values correctly.
Snapshots are, what you call "transactions".

s = StateRegistry.new

v = "good morning...." # => "good morning...."
p v.object_id # => xxxx

s.register(v)

snap1 = s.snapshot

v.gsub!(/morning/, "evening")
snap2 = s.snapshot

v.gsub!(/good/, "superb")
snap3 = s.snapshot

snap1.apply
p v # => "good morning...."
p v.object_id # => xxxx

snap2.apply
p v # => "good evening...."
p v.object_id # => xxxx

snap3.apply
p v # => "superb evening...."
p v.object_id # => xxxx


Of course this works with any number of registered objects. How a
snapshot is taken or restored is defined per-class/object (methods
take_snapshot and apply_snapshot).

http://ntecs.de/viewcvs/viewcvs/Wee/trunk/lib/wee/state_registry.rb?rev=289&view=auto
http://ntecs.de/viewcvs/viewcvs/Wee/trunk/lib/wee/snapshot.rb?rev=289&view=auto

I guess this could be used to implement transactions:

def transaction(*vars)
s = StateRegistry.new
vars.each do |v| s.register(v) end
snap = s.snapshot
begin
yield
rescue Exception
snap.apply # restore old snapshot
end
end

v = "good " # => "good "
transaction(v) do
v << "morning"
raise
end
p v # => "good " (and same __id__)


If this is not only usable for my framework, then I'll release this as
an extra library.

Regards,

Michael
 
A

Austin Ziegler

Hm, I've written a similar library for Wee, which saves snapshots of
objects, and is able to restore the old object from a snapshot. It does
not use Marshal, and maintains __id__ values correctly.
Snapshots are, what you call "transactions".

Taking a quick look at your library, it looks like it does much the
same as mine, except it uses an external object for transaction
management and, obviously, doesn't use Marshal. There are several
reasons that I implemented it this way, the most important being your
implementation of Array#take_snapshot: it dups the array:

irb#1(main):001:0> arr = %w(a b c d)
=> ["a", "b", "c", "d"]
irb#1(main):002:0> arr2 = arr.dup
=> ["a", "b", "c", "d"]
irb#1(main):003:0> arr2[0] << "z"
=> "az"
irb#1(main):004:0> arr
=> ["az", "b", "c", "d"]
irb#1(main):005:0>

Transaction::Simple is a "deep" transaction: it applies the
transaction to the object and all objects owned by the object.
Transaction::Simple was developed initially for PDF::Writer (and
generalised from there) because I needed to be able to have deep
transactions for PDF document pages, which might have to be reverted
to an initial state and reapplied in a different manner because we
reached the end of the page before we should have (e.g., paragraph
wrapping, although I think it's mostly used for table handling).

The StateRegistry only takes a shallow copy of the object references,
leading to a situation where I can revert to a snapshot but not have
objects referring to values as they were at the time of the snapshot.

Unfortunately, it's been so long since I wrote the paragraph above
that I don't recall what technique I was going to use to restore the
values of the object without destroying __id__. I do know that I
detect whether an object responds to #replace and use that if it's
present (thereby maintaining __id__). It's not foolproof -- as someone
could implement a #replace that doesn't do what we expect.

-austin
 
M

Michael Neumann

Taking a quick look at your library, it looks like it does much the
same as mine, except it uses an external object for transaction
management and, obviously, doesn't use Marshal. There are several

Yes, because a transaction (or snapshot) might involve any number of
objects.
reasons that I implemented it this way, the most important being your
implementation of Array#take_snapshot: it dups the array:

irb#1(main):001:0> arr = %w(a b c d)
=> ["a", "b", "c", "d"]
irb#1(main):002:0> arr2 = arr.dup
=> ["a", "b", "c", "d"]
irb#1(main):003:0> arr2[0] << "z"
=> "az"
irb#1(main):004:0> arr
=> ["az", "b", "c", "d"]
irb#1(main):005:0>

Right! And that's for my purposes a good thing, as I register objects
that I want to be "backtrackable" (it's more flexible this way).
Transaction::Simple is a "deep" transaction: it applies the
transaction to the object and all objects owned by the object.
Transaction::Simple was developed initially for PDF::Writer (and
generalised from there) because I needed to be able to have deep
transactions for PDF document pages, which might have to be reverted
to an initial state and reapplied in a different manner because we
reached the end of the page before we should have (e.g., paragraph
wrapping, although I think it's mostly used for table handling).

The StateRegistry only takes a shallow copy of the object references,
leading to a situation where I can revert to a snapshot but not have
objects referring to values as they were at the time of the snapshot.

Well, I could do a deep snapshot too, by simply registering all array
elements.

instead of:

s.register(obj)

you would have to write:

case obj
when Enumerable
obj.each do |val| s.register(val)
s.register(obj)
end

and apply this recursively, then you get a deep snapshot, without
loosing __id__.

Regards,

Michael
 
A

Austin Ziegler

This isn't as bad as it sounds. Most things that can't be Marshal-ed
can't be usefully snapshotted, either (e.g., IO objects).
Yes, because a transaction (or snapshot) might involve any number
of objects.

Right. But this can be accomplished with Transaction::Simple using
the block mechanism added in 1.2:

Transaction::Simple.start(a, b, c) do |ta, tb, tc|
...
end
reasons that I implemented it this way, the most important being
your implementation of Array#take_snapshot: it dups the array:

irb#1(main):001:0> arr = %w(a b c d)
=> ["a", "b", "c", "d"]
irb#1(main):002:0> arr2 = arr.dup
=> ["a", "b", "c", "d"]
irb#1(main):003:0> arr2[0] << "z"
=> "az"
irb#1(main):004:0> arr
=> ["az", "b", "c", "d"]
irb#1(main):005:0>

Right! And that's for my purposes a good thing, as I register
objects that I want to be "backtrackable" (it's more flexible this
way).

Mmm. More flexible, but more work. When I want a transaction on an
object, I want its full state kept. This, to me, is useless:

a = %w(a b c) # 'a', 'b', 'c'
# take a snapshot
a << "d" # 'a', 'b', 'c', 'd'
a[0] << "z" # 'az', 'b', 'c', 'd'
# restore # 'az', 'b', 'c'

Simply taking snapshots of the current object references (which is
what your snapshot process does) only performs part of the necessary
behaviour for true transaction support. I actually was trying
something like this (not as formalised, certainly) with PDF::Writer
and was running into problems because my objects were changing even
when I didn't want them to change, which is why I created
Transaction::Simple.

I have not yet encountered any times when I've used T...::Simple
that the loss of __id__ is a problem. I suspect that in a stateful
environment (appservers, for example) this is more likely to be a
problem, but very little that I work with at this point is stateful.

Ideally, I'd love to see Object#__replace__ that only works on
objects of the same immediate class, replacing the internal
representation of the object but maintaining the __id__ just like
Array#replace does now. This way, I'd be relatively safe from
potential rewrites of #replace and still maintain the __id__.

-austin
 
M

Michael Neumann

reasons that I implemented it this way, the most important being
your implementation of Array#take_snapshot: it dups the array:

irb#1(main):001:0> arr = %w(a b c d)
=> ["a", "b", "c", "d"]
irb#1(main):002:0> arr2 = arr.dup
=> ["a", "b", "c", "d"]
irb#1(main):003:0> arr2[0] << "z"
=> "az"
irb#1(main):004:0> arr
=> ["az", "b", "c", "d"]
irb#1(main):005:0>

Right! And that's for my purposes a good thing, as I register
objects that I want to be "backtrackable" (it's more flexible this
way).

Mmm. More flexible, but more work. When I want a transaction on an
object, I want its full state kept. This, to me, is useless:

a = %w(a b c) # 'a', 'b', 'c'
# take a snapshot
a << "d" # 'a', 'b', 'c', 'd'
a[0] << "z" # 'az', 'b', 'c', 'd'
# restore # 'az', 'b', 'c'

Sure. My problem indeed is completely different. I register callbacks
for components which live in a "tree". Then I want to be able to make
snapshots and go back in time. I can't use marshal (or dup), without
loosing the callbacks (or use an external representation of the
components, e.g. as IOWA does with it's positional component id's).
Simply taking snapshots of the current object references (which is
what your snapshot process does) only performs part of the necessary
behaviour for true transaction support. I actually was trying
something like this (not as formalised, certainly) with PDF::Writer
and was running into problems because my objects were changing even
when I didn't want them to change, which is why I created
Transaction::Simple.

Well, it's up to you whether they change or not, or whether you register
them for being part of the transaction or not. But sure, deep-cloning is
probably much more user-friendly.

Regards,

Michael
 
M

Mauricio Fernández

Ideally, I'd love to see Object#__replace__ that only works on
objects of the same immediate class, replacing the internal

That sounds like Object#become, as found in evil.rb, with the
restrictions imposed by the current Ruby implementation...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top