[ANN] Transaction::Simple 1.2.0

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
--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

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

···

On Thu, Nov 04, 2004 at 12:13:20PM +0900, Austin Ziegler wrote:

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.

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

···

On Thu, 4 Nov 2004 12:48:14 +0100, Michael Neumann <mneumann@ntecs.de> wrote:

On Thu, Nov 04, 2004 at 12:13:20PM +0900, Austin Ziegler wrote:
> 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".

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

> > 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".

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

···

On Thu, Nov 04, 2004 at 08:37:40AM -0500, Austin Ziegler wrote:

On Thu, 4 Nov 2004 12:48:14 +0100, Michael Neumann <mneumann@ntecs.de> wrote:
> On Thu, Nov 04, 2004 at 12:13:20PM +0900, Austin Ziegler wrote:

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.

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).

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.

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

···

On Thu, 4 Nov 2004 15:21:49 +0100, Michael Neumann <mneumann@ntecs.de> wrote:

On Thu, Nov 04, 2004 at 08:37:40AM -0500, Austin Ziegler wrote:

On Thu, 4 Nov 2004 12:48:14 +0100, Michael Neumann >> <mneumann@ntecs.de> wrote:

On Thu, Nov 04, 2004 at 12:13:20PM +0900, Austin Ziegler wrote:

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

On Thu, 4 Nov 2004 15:21:49 +0100, 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

···

On Thu, Nov 04, 2004 at 10:08:17AM -0500, Austin Ziegler wrote:

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...

···

On Fri, Nov 05, 2004 at 12:08:20AM +0900, Austin Ziegler wrote:

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__.

--
Hassle-free packages for Ruby?
RPA is available from http://www.rubyarchive.org/