,INBerkeley DB Reference Guide: Transaction Protected Applications[PH

Berkeley DB Reference Guide: Transaction Protected Applications



A

Building transaction protected applications



PCreating transaction protected applications using the Berkeley DB access methodsmis quite easy. In almost all cases, applications use db_appinitOto perform initialization of all of the Berkeley DB subsystems. As transactionsupport requires all five Berkeley DB subsystems, the DB_INIT_MPOOLDB_INIT_LOCK, DB_INIT_LOG and DB_INIT_TXN flagsshould be specified.

kOnce the application has called db_appinit, it should open theFdatabases it will use. Once the databases are opened, the applicationGcan make changes to the databases, grouped inside of transaction calls.;Each set of changes should be surrounded by the appropriate¯txn_begin, txn_commit and txn_abort calls.

MThe Berkeley DB access methods will make the appropriate calls into the lock,Klog and memory pool subsystems in order to guarantee transaction semantics.OWhen the application is ready to exit, all outstanding transactions should havebeen committed or aborted.

ADatabases accessed by a transaction must not be closed during theFtransaction. Once all outstanding transactions are finished, all openRBerkeley DB files should be closed. When the Berkeley DB database files have beenlclosed, the environment should be closed by calling db_appexit.

IIt is not necessary to transaction protect read-only transactions, unlessJthose transactions require repeatable reads. However, if there are updateItransactions present in the database, the reading transactions must stillEuse locking, and should be prepared to repeat any operation (possiblyJclosing and reopening a cursor) which fails with a return value of EAGAIN.

DConsider an application suite where multiple processes (or, multipleIthreads of control in a single process, for that matter) are changing theJvalues associated with a key in one or more databases. Specifically, theyGare taking the current value, incrementing it, and then storing it backHinto the database. There are two reasons that such an application needsHtransactions (other than the obvious requirement of recoverability afterapplication or system failure):

JThe first additional reason for transactions is that the application needsHatomicity, i.e., since we want to change a value that's currently in theGdatabase, we have to make sure that once we read it, no other thread ofDcontrol modifies it. For example, let's say that both thread #1 andGthread #2 are doing similar operations in the database, where thread #1Iis incrementing records by 3, and thread #2 is incrementing records by 5.FWhat we want to happen is to increment the record by a total of 8. IfGthe operations interleave in the right (well, wrong) order, that is notwhat will happen:



IAs you can see, instead of incrementing the record by a total of 8, we'veAonly incremented it by 3, because thread #1 overwrote thread #2'sHchange. By wrapping the operations in transactions, we ensure that thisIcannot happen. In a transaction, when the first thread reads the record,Blocks are acquired that will not be released until the transactionEfinishes, guaranteeing that all other readers and writers will block,Jwaiting for the first thread's transaction to complete (or to be aborted).

GThe second additional reason is that when multiple processes or threadsAof control are modifying the database, there is the potential forLdeadlock. In Berkeley DB, deadlock is signified by an error return from theBerkeley DB function of EAGAIN.

FHere is an example function that does transaction protected incrementson database records:



LIn applications supporting transactions, note that all Berkeley DB functionsFhave an additional possible error return: EAGAIN. In the above sampleLcode, you can see that any time the Berkeley DB function returns EAGAIN, thegtransaction is aborted (txn_abort, which releases all of itsHresources), and then the transaction is retried, from scratch. There isGno requirement that the transaction be attempted again, but that is the$common thing for applications to do.

EThere is one additional error that transaction protected applications<need to handle, and which is not shown in the above example.

LThere exists a class of errors that Berkeley DB considers fatal to an entireIBerkeley DB environment. An example of this type of error is a log writeHfailure due to the disk being out of free space. The only way to recoverGfrom these failures is for the application to exit, run recovery of theUBerkeley DB environment, and re-enter Berkeley DB. (It is not strictly necessary thatEthe application exit, although that is the only way to recover systemDresources, e.g., file descriptors and memory, currently allocated by Berkeley DB.)

7When this type of error is encountered, the error value=DB_RUNRECOVERY is returned. This error can be returned by anyFBerkeley DB interface. If a fatal error occurs, DB_RUNRECOVERY will beOreturned from all subsequent Berkeley DB calls made by any threads or processes!participating in the environment.

IOptionally, applications may also specify a fatal-error callback function@by setting the db_paniccall field of the DB_ENV structure beforeiinitializing the environment with db_appinit. This callbackEfunction will be called with two arguments: a reference to the DB_ENV8Estructure associated with the environment, and the errno valuer=associated with the underlying error that caused the problem.B

rIApplications can handle such fatal errors in one of two ways: by checkingtCfor DB_RUNRECOVERY as part of their normal Berkeley DB error return"Gchecking, similarly to EAGAIN in the above example, or, in applications,Cthat have no cleanup processing of their own, by simply exiting thea1application when the callback function is called.

>M AMoq.sÿÿall cases, applications use db_appinitOto perform initialization of all of the Berkeley DB subsystems. As transactionsupport requires all five Berkeley DB subsystems, the