[ANN] Transaction::Simple 1.2.0

Discussion in 'Ruby' started by Austin Ziegler, Nov 4, 2004.

  1. 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 *
    * Alternate:
     
    Austin Ziegler, Nov 4, 2004
    #1
    1. Advertising

  2. 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/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
     
    Michael Neumann, Nov 4, 2004
    #2
    1. Advertising

  3. On Thu, 4 Nov 2004 12:48:14 +0100, Michael Neumann <> 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 *
    * Alternate:
     
    Austin Ziegler, Nov 4, 2004
    #3
  4. On Thu, Nov 04, 2004 at 08:37:40AM -0500, Austin Ziegler wrote:
    > On Thu, 4 Nov 2004 12:48:14 +0100, Michael Neumann <> 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
     
    Michael Neumann, Nov 4, 2004
    #4
  5. On Thu, 4 Nov 2004 15:21:49 +0100, Michael Neumann
    <> 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
    >> <> 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 *
    * Alternate:
     
    Austin Ziegler, Nov 4, 2004
    #5
  6. 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
     
    Michael Neumann, Nov 4, 2004
    #6
  7. 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/
     
    Mauricio Fernández, Nov 4, 2004
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Vencz Istv?n
    Replies:
    2
    Views:
    310
  2. Austin Ziegler

    [ANN] Transaction::Simple

    Austin Ziegler, Jul 9, 2003, in forum: Ruby
    Replies:
    0
    Views:
    111
    Austin Ziegler
    Jul 9, 2003
  3. Austin Ziegler

    [ANN] Transaction::Simple 1.1.0

    Austin Ziegler, Aug 23, 2003, in forum: Ruby
    Replies:
    0
    Views:
    88
    Austin Ziegler
    Aug 23, 2003
  4. Austin Ziegler

    [ANN] Transaction::Simple 1.3.0

    Austin Ziegler, May 5, 2005, in forum: Ruby
    Replies:
    2
    Views:
    138
    Austin Ziegler
    May 5, 2005
  5. Austin Ziegler

    [ANN] Transaction::Simple 1.4.0 Released

    Austin Ziegler, Feb 4, 2007, in forum: Ruby
    Replies:
    2
    Views:
    99
    Gregory Brown
    Feb 5, 2007
Loading...

Share This Page