=head1 NAME
Object::Transaction - Virtual base class for transactions on files containing serialized hash objects
=head1 SYNOPSIS
use Object::Transaction;
transaction($coderef, @codeargs);
commit();
abandon();
$there_is_a_pending_transaction = transaction_pending()
package Pkg;
@ISA = qw(Object::Transaction);
use Object::Transaction;
$obj = sub new { ... }
sub file($ref,$id) { ... }
$obj = load Pkg $id;
$obj->savelater();
$obj->save();
$obj->removelater();
$obj->remove();
$obj->commit();
$obj->uncache();
$obj->abandon();
$oldobj = $obj->old();
$reference = $obj->objectref();
$obj = $reference->loadref();
$id = sub id { ... }
$restart_commit = sub precommit() { }
@passby = sub presave($old) { ... }
sub postsave($old,@passby) { ... }
$newid = sub preload($id) { .... }
sub postload() { ... }
sub preremove() { ... }
sub postremove() { ... }
=head1 DESCRIPTION
B<Object::Transaction> provides transaction support for hash-based objects that
are stored one-per-file using Storable. Multiuser access is supported.
In the future, serializing methods other than Storable will be supported.
B<Object::Transaction> is a virtual base class. In order to use it, you must
inherit from it and override the C<new> method and the C<file>
method.
Optomistic locking is used: it is possible that a transaction will fail
because the data that is is based upon has changed out from under it.
=head1 EXAMPLE
package User;
@ISA = qw(Object::Transaction);
use Object::Transaction;
my $top = "/some/path";
sub new {
my ($package, $login) = @_;
die unless getpwnam($login);
return bless { UID => getpwnam($login) };
}
sub file {
my ($ref, $id) = @_;
$id = $ref->id() unless $id;
return "$top/users/$id/data.storable";
}
sub id {
my ($this) = @_;
return $this->{UID};
}
sub preload
{
my ($id) = @_;
return if getpwuid($id);
return getpwnam($id) if getpwnam($id);
die;
}
sub postload
{
my ($this) = @_;
my ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,
$shell,$expire) = getpwuid($this->{UID});
$this->{SHELL} = $shell;
}
sub presave
{
my ($this, $old) = @_;
my $id = $this->{UID};
mkdir("$top/users/$id", 0700);
delete $this->{SHELL};
}
sub postsave
{
goto &postload;
}
sub postremove
{
delete from pw file...
}
my $joe = new User "joe";
$joe->savelater();
my $fred = new User "fred";
$fred->savelater();
$joe->commit();
=head1 METHODS PROVIDED
B<Object::Transaction> provides the following methods.
=over 15
=item C<load($id)>
C<load> is the way to bring an object into memory. It is usually
invoked as C<my $obj = load MyObject $id>.
There are two opportunities to customize the behavior of C<load>:
C<preload> for things that should happen before loading and
C<postload> for things that should happen after loading.
B<Object::Transaction> caches objects that are loaded. This is
done both for performance reasons and to make sure that only one
copy of an object is in memory at a time. If caching is not desired,
the C<uncache> method must be invoked after loading.
X<savelater>
=item C<savelater()>
C<savelater> is the usual method of saving an object. The object
is not saved at the time that C<savelater> is invoked. It is actually
saved when C<commit> is invoked.
There are two opportunities to customize the behavior of C<savelater>:
C<presave> for things that should happen before saving and
C<postsave> for things that should happen after saving. These
are invoked when the object is actually being saved.
=item C<save()>
Simply C<savelater> combined with a C<commit>.
X<removelater>
=item C<removelater()>
C<removelater> is the usual method of removing an object. The object
is not removed at the time that C<removelater> is invoked. It is actually
removed when C<commit> is invoked.
There are two opporunities to customize the behavior of C<removelater>:
C<preremove> for things that should happen before removing and
C<postremove> for things that should happen after removing. These
are invoked when the object is actually being removed.
=item C<remove()>
Simply C<removelater> combined with a C<commit>
X<commit>
=item C<commit()>
C<commit> writes all pending changes to disk. Either all changes
will be saved or none of them will. Deadlocks are avoided by locking
files in order.
B<Object::Transaction> uses opportunistic locking. Commit can fail.
If it fails, it will C<die> with a message that begins C<DATACHANGE: file>.
It is advisable to wrap your entire transaction inside an eval so
that it can be re-tried in the event that the data on disk changed
between the time is was loaded and commited.
In the event of a commit failure, the object cache will be reset.
Do not keep any old references to objects after such a failure. To
avoid keeping old references, it is advised that the first C<load()>
call happen inside the C<eval>.
=item C<transaction($funcref,@args)>
C<transaction()> is a wrapper for a complete transaction. Transactions
that fail due to opportunistic locking problems will be re-run automatically.
Beware side-effects!
The first parameter is a reference to a function. Any additional parameters
will be passed as parameters to that function.
The return value of C<transaction()> is the return value of C<&$funcref()>.
It is not necessary to use the C<transaction()> method. Just beware that
C<commit()>, C<save()>, and C<remove()> can fail. C<transaction()> will
keep trying until it suceeds; it failes for a reason other than
an opportunistic locking problem; or it gives up because it has
had too many (more than $ObjTransLclCnfg::maxtries) failures.
It is important that objects not be cached from one invocation of
C<transaction()> to another. The following would fail badly.
my $obj1 = load MyObject $obj1;
my $p = fork();
transaction(sub {
$obj1->savelater();
commit();
});
To fix it, move the object load to inside the C<transaction()> call.
=item C<transaction_pending()>
C<transaction_pending()> returns true if there is a transaction pending.
(savelater() called, but commit() not yet called).
X<abandon>
=item C<abandon()>
As an alternative to C<commit>, all changes may be abandoned. Calling
C<abandon()> does not undo changes made to the in-memory copies of objects.
X<uncache>
=item C<uncache()>
B<Object::Transaction> caches all objects. To flush an object from
B<Object::Transaction>'s cache, invoke the C<uncache> method on the
object.
Be careful when doing this -- it makes it possible to have more than
one copy of the same object in memory.
C<uncache()> can be invoked as a class method rather than an object
method (C<Object::Transaction->uncache()>). When invoked as a
class method, the entire cache is flushed.
=item C<readlock()>
By default B<Object::Transaction> does not lock objects unless they
are being modified.
The C<readlock()> method insures that objects are properly locked and
unchanged during a transaction even if they are not being modified.
C<savelater()> takes precedence over C<readlock()> so they can be combined
freely.
Paranoid programmers should use C<readlock()> on most objects.
C<readlock()> doesn't actually lock objects, it just verifies that
they haven't changed when the transaction commits.
=item C<old()>
Return the previous version of an object. Previous is only
loosely defined.
=item C<objectref()>
Objectref creates a tiny object that is a reference to an object. The
reference can be turned back into the object by invoking C<loadref()>.
For example:
$reference = $object->objectref();
$object = $reference->loadref();
The reference is suitable for persistant storage as a member in a
persistant object.
=item C<cache()>
Objects are cached so that multiple loads of the same identifier
result in only one object in memory. Newly created objects that
are created with C<Object::Transaction::new> will be put in the
cache immediately. If an object is created some other way, and there
is chance that it will be C<load()>ed before the tranaction commits,
there is the potential for a problem. Invoking C<cache()> puts an
object into the cache so that C<load()> won't fail.
=back
=head1 REQUIRED METHODS TO OVERRIDE
The following methods must be overriden.
=over 15
=item C<initialize>
B<Object::Transaction> provides a contructor. The constructor provide
delegates much of the work to a callback that you can override:
C<initialize()>.
=item C<file($ref,$id)>
You must provide a function that returns the filename that
an object is stored in. The C<file> method can be invoked
in two ways: as an object method call without an C<$id>
parameter; or as a class method call with an C<$id> parameter.
=back
=head1 OPTIONAL METHODS TO OVERRIDE
The following methods may be overridden.
=over 15
X<preload>
=item C<preload($id)>
C<preload()> is invoked as nearly the first step of C<load>. It is
generally used to make sure that the C<$id> is valid. C<preload()> is a
class method rather than an object method.
The return value of C<preload> is a replacement C<$id>. For example,
it might be called as C<preload("Joe")> to load the user named Joe, but
if users are numbered rather than named it could return the number
for Joe. A return value of undef is ignored.
No lock on the underlying file is present at the time C<preload> or
C<postload> is called.
=item C<postload($id)>
C<postload> is invoked after the object has been loaded into memory but
before transaction completeness is checked.
The underlying file is not locked at the time that C<postload> is invoked.
Previous versions of Object::Transaction locked the underlying object while
C<postload> was invoked.
If a transaction rollback is required, C<postload> will be invoked again
after the object has been reverted to its pre-transaction state.
X<presave>
=item C<presave($old)>
C<presave()> is invoked just before an object is written to disk.
Objects are stored on disk in the file specified by the
C<file> method. The directory in which that file resides
must exist by the time C<presave()> finishes. C<presave> should
make the directory if it isn't already made.
The underlying file may or may not be locked at the time C<presave> is
invoked.
C<presave> can be invoked as a side-effect of C<load> if the
object must roll back to a previous version.
The parameter C<$old> is a copy of the object as of the time it was
first loaded into memory.
Any return values from C<presave> will be remembered and passed to
C<postsave>.
C<presave> may not invoke C<save()>, C<commit()>, or C<savelater()>.
X<postsave>
=item C<postsave($old,@psv)>
C<postsave> is invoked after an object has been written to disk.
The underlying file is always locked at the time C<postsave> is
invoked.
Invocations of C<presave> and C<postsave> are always paired.
The parameter C<$old> is a copy of the object as of the time it was
first loaded into memory.
The parameter C<@psv> is the return value from C<presave>.
C<postsave> may not invoke C<save()>, C<commit()>, or C<savelater()>.
X<precommit>
=item C<precommit($old)>
C<precommit> is invoked just before files are locked in C<commit()>.
This is before C<presave()>.
Unlike C<presave()> and C<postsave()>, C<precommit()> may use
C<savelater()> to add new objects to the transaction. If it does
so, it must return a true value.
X<id>
=item C<id()>
B<Object::Transaction> expects to be able to find the unique identifier (id) for each
object as C<$obj->{'ID'}>. If that isn't the case, you can override
the C<id> function to provide an alternative.
=item C<new()>
The new method that Object::Transaction defines is minimal. It does a callback
to C<initialize()> as an additional hook for customization.
=back
=head1 PUBLIC MEMBER DATA
The following data members are used by B<Object::Transaction>.
=over 15
=item C<ID>
B<Object::Transaction> expect to find the id for an object in C<$obj->{'ID'}>. This
can be overridden by defining your own C<id> function.
=item C<OLD>
When an object is loaded into memory a copy is made. The copy can
be found at C<$obj->{OLD}>. The copy should not be modified.
The copy is explicitly passed to C<presave> and C<postsave>.
=back
=head1 PRIVATE MEMBER DATA
B<Object::Transaction> adds a few data members to each object for
its own internal use.
These are:
__frozen
__transfollowers
__transleader
__rollback
__removenow
__toremove
__transdata
__readonly
__trivial
__atcommit
__poison
None of these should be touched.
=head1 FUNCTIONS
There are a few functions exported by Object::Transaction. These
functions are also available as methods. They are C<transaction()>,
C<transaction_pending()>, C<uncache()>, C<commit()>, and C<abandon()>.
=head1 BUGS
A program or computer crash at just the wrong moment can allow an
object that should be deleted to escape deletion. Any future attempt
to access such an object will cause it to self-destruct.
In some situations objects will be saved even if niether C<save()> nor
C<savelater()> is invoked. This happens if C<readlock()> is used and the
transaction leader object (one per transaction) choosen turns out to be
an object for which only C<readlock()> was called.
=head1 AUTHOR
David Muir Sharnoff <muir@idiom.com>
=head1 COPYRIGHT
Copyright (C) 1999-2002, Internet Journals Corporation <www.bepress.com>.
Copyright (C) 2002, David Muir Sharnoff.
All rights reserved. License hearby granted for anyone to use this
module at their own risk. Please feed useful changes back to
David Muir Sharnoff <muir@idiom.com>.