Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > [ANN] Transaction::Simple 1.2.0

Reply
Thread Tools

[ANN] Transaction::Simple 1.2.0

 
 
Austin Ziegler
Guest
Posts: n/a
 
      11-04-2004
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 * http://www.velocityreviews.com/forums/(E-Mail Removed)
* Alternate: (E-Mail Removed)


 
Reply With Quote
 
 
 
 
Michael Neumann
Guest
Posts: n/a
 
      11-04-2004
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".

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/...=289&view=auto
http://ntecs.de/viewcvs/viewcvs/Wee/...=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


 
Reply With Quote
 
 
 
 
Austin Ziegler
Guest
Posts: n/a
 
      11-04-2004
On Thu, 4 Nov 2004 12:48:14 +0100, Michael Neumann <(E-Mail Removed)> 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".


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
--
Austin Ziegler * (E-Mail Removed)
* Alternate: (E-Mail Removed)


 
Reply With Quote
 
Michael Neumann
Guest
Posts: n/a
 
      11-04-2004
On Thu, Nov 04, 2004 at 08:37:40AM -0500, Austin Ziegler wrote:
> On Thu, 4 Nov 2004 12:48:14 +0100, Michael Neumann <(E-Mail Removed)> 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".

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


 
Reply With Quote
 
Austin Ziegler
Guest
Posts: n/a
 
      11-04-2004
On Thu, 4 Nov 2004 15:21:49 +0100, Michael Neumann
<(E-Mail Removed)> 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
>> <(E-Mail Removed)> 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
--
Austin Ziegler * (E-Mail Removed)
* Alternate: (E-Mail Removed)


 
Reply With Quote
 
Michael Neumann
Guest
Posts: n/a
 
      11-04-2004
On Thu, Nov 04, 2004 at 10:08:17AM -0500, Austin Ziegler wrote:
> 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


 
Reply With Quote
 
Mauricio Fernández
Guest
Posts: n/a
 
      11-04-2004
On Fri, Nov 05, 2004 at 12:08:20AM +0900, 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...

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


 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off




Advertisments