Why not adopt me?
NAME
Object::Transaction - Virtual base class for transactions on files containing serialized hash objects
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() { ... }
DESCRIPTION
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.
Object::Transaction is a virtual base class. In order to use it, you must inherit from it and override the new
method and the 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.
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();
METHODS PROVIDED
Object::Transaction provides the following methods.
load($id)
-
load
is the way to bring an object into memory. It is usually invoked asmy $obj = load MyObject $id
.There are two opportunities to customize the behavior of
load
:preload
for things that should happen before loading andpostload
for things that should happen after loading.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
uncache
method must be invoked after loading. savelater()
-
savelater
is the usual method of saving an object. The object is not saved at the time thatsavelater
is invoked. It is actually saved whencommit
is invoked.There are two opportunities to customize the behavior of
savelater
:presave
for things that should happen before saving andpostsave
for things that should happen after saving. These are invoked when the object is actually being saved. save()
-
Simply
savelater
combined with acommit
. removelater()
-
removelater
is the usual method of removing an object. The object is not removed at the time thatremovelater
is invoked. It is actually removed whencommit
is invoked.There are two opporunities to customize the behavior of
removelater
:preremove
for things that should happen before removing andpostremove
for things that should happen after removing. These are invoked when the object is actually being removed. remove()
-
Simply
removelater
combined with acommit
commit()
-
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.Object::Transaction uses opportunistic locking. Commit can fail. If it fails, it will
die
with a message that beginsDATACHANGE: 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
load()
call happen inside theeval
. transaction($funcref,@args)
-
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
transaction()
is the return value of&$funcref()
.It is not necessary to use the
transaction()
method. Just beware thatcommit()
,save()
, andremove()
can fail.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
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
transaction()
call. transaction_pending()
-
transaction_pending()
returns true if there is a transaction pending. (savelater() called, but commit() not yet called). abandon()
-
As an alternative to
commit
, all changes may be abandoned. Callingabandon()
does not undo changes made to the in-memory copies of objects. uncache()
-
Object::Transaction caches all objects. To flush an object from Object::Transaction's cache, invoke the
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.
uncache()
can be invoked as a class method rather than an object method (Object::Transaction-
uncache()>). When invoked as a class method, the entire cache is flushed. readlock()
-
By default Object::Transaction does not lock objects unless they are being modified.
The
readlock()
method insures that objects are properly locked and unchanged during a transaction even if they are not being modified.savelater()
takes precedence overreadlock()
so they can be combined freely.Paranoid programmers should use
readlock()
on most objects.readlock()
doesn't actually lock objects, it just verifies that they haven't changed when the transaction commits. old()
-
Return the previous version of an object. Previous is only loosely defined.
objectref()
-
Objectref creates a tiny object that is a reference to an object. The reference can be turned back into the object by invoking
loadref()
. For example:$reference = $object->objectref(); $object = $reference->loadref();
The reference is suitable for persistant storage as a member in a persistant object.
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
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 beload()
ed before the tranaction commits, there is the potential for a problem. Invokingcache()
puts an object into the cache so thatload()
won't fail.
REQUIRED METHODS TO OVERRIDE
The following methods must be overriden.
initialize
-
Object::Transaction provides a contructor. The constructor provide delegates much of the work to a callback that you can override:
initialize()
. file($ref,$id)
-
You must provide a function that returns the filename that an object is stored in. The
file
method can be invoked in two ways: as an object method call without an$id
parameter; or as a class method call with an$id
parameter.
OPTIONAL METHODS TO OVERRIDE
The following methods may be overridden.
preload($id)
preload()
is invoked as nearly the first step of load
. It is generally used to make sure that the $id
is valid. preload()
is a class method rather than an object method.
The return value of preload
is a replacement $id
. For example, it might be called as 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 preload
or postload
is called.
postload($id)
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 postload
is invoked. Previous versions of Object::Transaction locked the underlying object while postload
was invoked.
If a transaction rollback is required, postload
will be invoked again after the object has been reverted to its pre-transaction state.
presave($old)
presave()
is invoked just before an object is written to disk.
Objects are stored on disk in the file specified by the file
method. The directory in which that file resides must exist by the time presave()
finishes. presave
should make the directory if it isn't already made.
The underlying file may or may not be locked at the time presave
is invoked.
presave
can be invoked as a side-effect of load
if the object must roll back to a previous version.
The parameter $old
is a copy of the object as of the time it was first loaded into memory.
Any return values from presave
will be remembered and passed to postsave
.
presave
may not invoke save()
, commit()
, or savelater()
.
postsave($old,@psv)
postsave
is invoked after an object has been written to disk.
The underlying file is always locked at the time postsave
is invoked.
Invocations of presave
and postsave
are always paired.
The parameter $old
is a copy of the object as of the time it was first loaded into memory.
The parameter @psv
is the return value from presave
.
postsave
may not invoke save()
, commit()
, or savelater()
.
precommit($old)
precommit
is invoked just before files are locked in commit()
. This is before presave()
.
Unlike presave()
and postsave()
, precommit()
may use savelater()
to add new objects to the transaction. If it does so, it must return a true value.
id()
Object::Transaction expects to be able to find the unique identifier (id) for each object as $obj-
{'ID'}>. If that isn't the case, you can override the id
function to provide an alternative.
new()
The new method that Object::Transaction defines is minimal. It does a callback to initialize()
as an additional hook for customization.
PUBLIC MEMBER DATA
The following data members are used by Object::Transaction.
ID
-
Object::Transaction expect to find the id for an object in
$obj-
{'ID'}>. This can be overridden by defining your ownid
function. OLD
-
When an object is loaded into memory a copy is made. The copy can be found at
$obj-
{OLD}>. The copy should not be modified. The copy is explicitly passed topresave
andpostsave
.
PRIVATE MEMBER DATA
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.
FUNCTIONS
There are a few functions exported by Object::Transaction. These functions are also available as methods. They are transaction()
, transaction_pending()
, uncache()
, commit()
, and abandon()
.
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 save()
nor savelater()
is invoked. This happens if readlock()
is used and the transaction leader object (one per transaction) choosen turns out to be an object for which only readlock()
was called.
AUTHOR
David Muir Sharnoff <muir@idiom.com>
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>.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 327:
You can't have =items (as at line 331) unless the first thing after the =over is an =item