Transaction rollback functions


Detailed Description

While writing Tornado last year, I kept finding myself writing little auto-instantiated in-place classes to undo an operation should an exception occur half way through it. I was experiencing a major problem of exception aware code - how do you guarantee data-consistency when any statement can throw an exception?

At the time of writing (June 2003), a majority of programmers just prefer to label fully exception-aware code as a mixture of being impossible or too much work/overhead to implement. However, it is my belief that like pervasive multithreading and multiprocessing, fully exception-aware code will become the defacto standard in the future. The parts of TnFOX written by me are all fully exception-aware.

Before I began TnFOX, I wanted to implement transaction support. Basically that means replacing those custom-written little classes with something more convenient. By sheer coincidence, I happened to fail a job interview with Trolltech because I didn't know anything about compile time metaprogramming, so this led me to buying Andrei Alexandrescu's Modern C++ Design which then led me to think I could solve this by getting the compiler to do it for me.

However, searching on google showed that Andrei Alexandrescu had had exactly the same problem and in his December 2000 CUJ article he gave an almost entirely compile-time solution (as much as it could be). From some ideas in that, I have come up with the functions listed below which can be almost as efficient as Andrei's when no rollbacks occur (around 15-20 instructions in x86). Furthermore, it offers a considerable superset of functionality including you not needing to FXRESTRICT your rollbacks to the local scope.

Usage:

Firstly, if you merely want a pointer deleted on scope exit, please see FX::FXPtrHold which is much more useful than at first sight (it can used statically to clean up fire-and-forget static allocations). For more complex operations such as the calling of a specific function or method with certain parameters, then FXRollback is the right choice.

Usage is ridiculously easy. For example:

{
    QPtrList<Item> *mylist;
    // I need to atomically append "item" to three lists
    QMtxHold h(mylist);
    mylist->append(item);
    FXRBOp undomylist=FXRBObj(*mylist, &QPtrList<Item>::removeRef, item);
    myotherlist->append(item);
    FXRBOp undomyotherlist=FXRBObj(*myotherlist, &QPtrList<Item>::removeRef, item);
    yetotherlist->append(item);
    undomyotherlist.dismiss();
    undomylist.dismiss();
}
A good point here is use of the contained scope - it prevents namespace clutter by making all the extra variable declarations go away. If the append to myotherlist or yetotherlist throws an exception, the rollback operations are destructed and thus the operation is undone - thus data integrity is maintained. For functions:
FXRollbackOp appfatalbit=FXRBFunc(::exit, 1);
... bits which could throw an exception and the app would have to immediately exit
appfatalbit.dismiss(); // It's over
Sufficient overloads are provided for four parameters via either method. It's trivial to extend further however and since all the relevent code is contained in FXRollback.h, you can just edit the header file (warning: the code is really nasty).

Warning:
All FXRB* functions take references, not values. This means that if you're passing basic types and then altering them with the expectation that the rollback will restore their original values, you'll find they won't be. Solution: take a copy into a stack allocated variable and pass those instead.

Groups:

You should try to use the above wherever possible as they are written to be extremely efficient in both code size and run-time overhead (in the normal case, all code executed is inlined). However there are times when you have a complex set of variable operations to be undertaken, all of which need to be rolled back on exception. The efficient use above cannot be used because FXRollbackOp must be created at the point of declaration ie; you can't declare an empty FXRollbackOp in a higher scope and assign to it during an if() statement say (this is because of the way FXRollbackOp is implemented to avoid a virtual destructor call). In this situation, you should declare a FXRollbackGroup and add to it:

void class::method(Item *i)
{
    FXRollbackGroup rbg;
    if(shouldAlterA)
    {
        alist1.append(i);
        rbg.add(FXRBObj(alist1, &QPtrList<Item>::removeRef, i));
    }
    if(shouldAlterB)
    {
        blist1.append(i);
        rbg.add(FXRBObj(blist1, &QPtrList<Item>::removeRef, i));
    }
    ...
    rbg.dismiss();
}
This also permits a try...finally operation - simply don't dismiss().

As of v0.80, you can pass a null code pointer to disable the operation eg;

int refCount=insertObject(obj);
FXRBOp uninsert=FXRBObj(*this, refCount>1 ? 0 : &Me::killObject, obj);
This means you can avoid groups for simple cases (more efficient).

Exception handling

Of course the big danger when using destructors to roll back operations interrupted by an exception being thrown is the dreaded ISO C++ guaranteed std::terminate() if another exception happens. However, TnFOX's superior nested exception support means that that is guaranteed not to happen with proper use of CppMunge.py <yay!>.

Exceptions during construction:

To guard against not cleaning up partially constructed objects, you should place a FXRBConstruct(this) first thing in any constructor which might throw an exception and use my two-stage object construction idiom. Further details are given in the doc page for FX::FXException.

See also:
FX::FXPtrHold, FX::FXRollbackGroup, FX::Generic::DoUndo


Classes

class  FX::FXRollbackGroup
 Lets you place a rollback in a group and/or a non-local scope. More...

Functions

FXRollbackOp FX::FXRBFunc (function,[, par1 &[, par2 &[, par3 &[, par4 &]]]])
FXRollbackOp FX::FXRBObj (object, ptrtomethod,[, par1 &[, par2 &[, par3 &[, par4 &]]]])
FXRollbackOp FX::FXRBNew (ptrtonewedalloc &)
FXRollbackOp FX::FXRBNewA (ptrtonewedalloc &)
FXRollbackOp FX::FXRBAlloc (ptrtomem)
FXRollbackOp FX::FXRBConstruct (thisptr)


(C) 2002-2009 Niall Douglas. Some parts (C) to assorted authors.
Generated on Fri Nov 20 18:31:36 2009 for TnFOX by doxygen v1.4.7