diff -r 000000000000 -r 08ec8eefde2f persistentstorage/dbms/utable/UT_TRANS.CPP --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/persistentstorage/dbms/utable/UT_TRANS.CPP Fri Jan 22 11:06:30 2010 +0200 @@ -0,0 +1,740 @@ +// Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// + +#include "UT_STD.H" + +// Class RDbTransaction::CNotifier + +NONSHARABLE_CLASS(RDbTransaction::CNotifier) : public CDbNotifier + { +public: + inline CNotifier( RDbTransaction& aTransaction ); + ~CNotifier(); +// + void Event( RDbNotifier::TEvent aEvent ); +private: + void Complete( TInt aStatus ); +// from CDbNotifier + void Notify( TType aEvent, TRequestStatus& aStatus ); + void Cancel(); +private: + RDbTransaction* iTransaction; + TRequestStatus* iStatus; + TInt iPending; + }; + +inline RDbTransaction::CNotifier::CNotifier( RDbTransaction& aTransaction ) + : iTransaction( &aTransaction ) + {} + +RDbTransaction::CNotifier::~CNotifier() +// +// Cancel any outstanding request and extract from the transaction +// + { + Cancel(); + if ( iTransaction ) + { + __ASSERT( iTransaction->iNotifier == this ); + iTransaction->iNotifier = 0; + } + } + +void RDbTransaction::CNotifier::Complete( TInt aStatus ) + { + if ( iStatus ) + { + iPending = 0; + User::RequestComplete( iStatus, aStatus ); + } + } + +void RDbTransaction::CNotifier::Notify( CDbNotifier::TType aType, TRequestStatus& aStatus ) +// +// Request for future notification. If the database is closed complete immediately +// + { + __ASSERT( !iStatus ); + __ASSERT( iPending >= 0 ); + iStatus = &aStatus; + if ( iPending > RDbNotifier::EUnlock ) + Complete( iPending ); + else if ( !iTransaction ) + Complete( RDbNotifier::EClose ); + else + { + iPending = aType; + aStatus = KRequestPending; + } + } + +void RDbTransaction::CNotifier::Cancel() + { + Complete( KErrCancel ); + } + +void RDbTransaction::CNotifier::Event( RDbNotifier::TEvent aEvent ) + { + if ( aEvent == RDbNotifier::EClose ) + iTransaction = 0; + if ( iStatus ) + { + __ASSERT( iPending < 0 ); + if (aEvent == RDbNotifier::EUnlock && iPending == CDbNotifier::EChange ) + ; // not interested in unlock events + else + Complete( aEvent ); + } + else + { + __ASSERT( iPending >= 0 ); + if ( aEvent > iPending ) + iPending = aEvent; // save the event + } + } + + +// Class RDbTransaction + +#ifdef _ASSERTIONS + +void RDbTransaction::_Invariant() const +// +// Invariance test +// + { + if ( iLockCount == 0 ) + { // nothing must be happening in this state + __ASSERT( iLockState == EDbReadLock ); + __ASSERT( iAction == EDbReadLock ); + __ASSERT( iUpdaters == 0 ); + return; + } + switch ( iLockState & EState ) + { + default: + __ASSERT( 0 ); + case EDbReadLock: + { + __ASSERT( iAction == EDbReadLock ); + __ASSERT( iLockCount > 0 ); // someone must have a lock + __ASSERT( iLockCount <= iMaxLock ); + __ASSERT( iUpdaters == 0 ); + __ASSERT( iPrimary.iState != 0 ); + for (TInt ii = iLockCount - 1; --ii >= 0; ) + __ASSERT( iSharers[ii].iState != 0 ); + } + break; + case EDbCompactLock: // not allowed in user-transactions + case EDbRecoveryLock: + __ASSERT( iAction == iLockState ); + __ASSERT( iLockCount == 1 ); // exactly one lock allowed + __ASSERT( iUpdaters == 0 ); + __ASSERT( iPrimary.iState == 0 ); + break; + case EDbXReadLock: // intention to write. No updates but exclusive + __ASSERT( iLockCount == 1 ); // exactly one lock allowed + switch ( iAction ) + { + default: + __ASSERT( 0 ); + case EDbReadLock: // must be in a transaction: cannot commit a write/schema mod when releasing a read lock + __ASSERT( iUpdaters == 0 ); + __ASSERT( iPrimary.iState & static_cast( ETransactionLock ) ); + break; + case EDbWriteLock: + __ASSERT( iUpdaters > 0 ); + break; + } + break; + case EDbWriteLock: + case EDbSchemaLock: + __ASSERT( iLockCount == 1 ); // exactly one lock allowed + switch ( iAction ) + { + default: + __ASSERT( 0 ); + case EDbReadLock: // must be in a transaction: cannot commit a write/schema mod when releasing a read lock + __ASSERT( iUpdaters == 0 ); + __ASSERT( iPrimary.iState & static_cast( ETransactionLock ) ); + break; + case EDbWriteLock: + __ASSERT( iUpdaters > 0 ); + __ASSERT( ( iLockState & EState ) == EDbWriteLock || ( iPrimary.iState & static_cast( ETransactionLock ) ) ); + break; + case EDbSchemaLock: + __ASSERT( ( iLockState & EState ) == EDbSchemaLock ); + __ASSERT( iUpdaters == 0 ); + break; + } + break; + } + } + +template struct _InvariantFunc + { + static void Invariant( TAny* aPtr ) { ( (const T*)aPtr )->_Invariant(); } + }; + +template inline TCleanupOperation _InvariantFunction( T* ) + { return _InvariantFunc::Invariant; } + +struct _Invariant + { + inline _Invariant( TCleanupOperation aOp, TAny* aPtr ) + : iOp( aOp ), iPtr( aPtr ) + { aOp( aPtr ); } + inline ~_Invariant() + { iOp( iPtr ); } +private: + TCleanupOperation iOp; + TAny* iPtr; + }; + +#ifndef __LEAVE_EQUALS_THROW +struct _InvariantL + { + inline _InvariantL( TCleanupOperation aOp, TAny* aPtr ) + { aOp( aPtr ); CleanupStack::PushL( TCleanupItem( aOp, aPtr ) ); } + inline ~_InvariantL() + { CleanupStack::PopAndDestroy(); } + }; +#endif //__LEAVE_EQUALS_THROW__ + +#define __INVARIANT struct _Invariant _invariant( _InvariantFunction( this ), this ); + +#ifdef __LEAVE_EQUALS_THROW__ + #define __INVARIANT_L __INVARIANT +#else + #define __INVARIANT_L struct _InvariantL _invariant( _InvariantFunction( this ), this ); +#endif //__LEAVE_EQUALS_THROW__ + +#else // _ASSERTIONS + +#define __INVARIANT ( (void)0 ) +#define __INVARIANT_L ( (void)0 ) + +#endif // _ASSERTIONS + +inline TDbLockType RDbTransaction::LockState() const + { return TDbLockType( iLockState & EState ); } + +void RDbTransaction::Close() + { + __ASSERT( !IsLocked() ); + User::Free( iSharers ); + Event( RDbNotifier::EClose ); + } + +void RDbTransaction::DoCommitL() +// +// Commit any changes +// + { + __ASSERT_ALWAYS( ( iPrimary.iState & ~ETransactionLock ) == 0, Panic( EDbStreamsPendingOnCommit ) ); + iLockState |= EFailed; + Database().FlushL( LockState() ); + Database().SynchL( LockState() ); + Unlock( RDbNotifier::ECommit ); + } + +void RDbTransaction::DoRollback() +// +// Rollback any changes +// + { + __ASSERT_ALWAYS( ( iPrimary.iState & ~ETransactionLock ) == 0, Panic( EDbStreamsPendingOnRollback ) ); + Database().Revert( LockState() ); + Database().Abandon( LockState() ); + if ( LockState() >= EDbWriteLock ) + ++iRollback; + Unlock( RDbNotifier::ERollback ); + } + +// explicit transactions + +void RDbTransaction::BeginL( const CDbObject& aObject ) +// +// begin a user transaction. This first gains a shared read-lock +// + { + __INVARIANT_L; + __ASSERT_ALWAYS( GetLock( aObject ) == 0, Panic( EDbBeginNestedTransaction ) ); + ReadyL(); + PrepareSLockL( aObject, TUint( ETransactionLock ) ); + __ASSERT( iAction == EDbReadLock ); + __ASSERT( iLockState == EDbReadLock ); + ++iLockCount; + } + +void RDbTransaction::CommitL( const CDbObject& aObject ) +// +// Commit a user transaction and release the lock +// All updates must be complete for a write-lock +// + { + __INVARIANT_L; + __ASSERT_ALWAYS( InTransaction( aObject ), Panic( EDbNoCurrentTransaction ) ); + ReadyL(); + if ( iLockCount > 1 ) + { + TLock* lock = GetLock( aObject ); + __ASSERT( lock ); + __ASSERT_ALWAYS( lock->iState == static_cast( ETransactionLock ), Panic( EDbStreamsPendingOnCommit ) ); + Unlock( *lock ); + } + else + { + __ASSERT_ALWAYS( iAction == EDbReadLock, Panic( EDbUpdatesPendingOnCommit ) ); + DoCommitL(); + } + } + +void RDbTransaction::Rollback( const CDbObject& aObject ) +// +// Rollback a user transaction and release the lock +// All updates must be complete/aborted for a write-lock +// + { + __INVARIANT; + __ASSERT_ALWAYS( InTransaction( aObject ), Panic( EDbNoCurrentTransaction ) ); + if ( iLockCount > 1 ) + { + TLock* lock = GetLock( aObject ); + __ASSERT( lock ); + __ASSERT_ALWAYS( lock->iState == static_cast( ETransactionLock ), Panic( EDbStreamsPendingOnRollback ) ); + Unlock( *lock ); + } + else + { + __ASSERT_ALWAYS( iAction == EDbReadLock, Panic( EDbUpdatesPendingOnRollback ) ); + DoRollback(); + } + } + +void RDbTransaction::PrepareSLockL( const CDbObject& aObject, TUint aInitState ) +// +// prepare to acquire a shared read lock +// if any holder has an exclusive lock this fails +// + { + __ASSERT( GetLock( aObject ) == 0 ); // cannot get a 2nd shared lock +// + THolder h = aObject.Context(); + if ( iLockCount == 0 ) + { + iPrimary.iHolder = h; // first lock, no other checks required + iPrimary.iState = aInitState; + } + else if ( iLockState != EDbReadLock ) + __LEAVE( KErrLocked ); + else + { // allocate a Sharers-slot + TLock* share = iSharers; + if ( iLockCount == iMaxLock ) + { + TInt newsize = iMaxLock + ELockListGranularity; + if ( newsize > EMaxLock ) + { + __LEAVE( KErrLocked ); + return; + } + iSharers = share = ( TLock* )User::ReAllocL( share, ( newsize - 1 ) * sizeof( TLock ) ); + iMaxLock = TUint8( newsize ); + } + share += iLockCount - 1; + share->iHolder = h; + share->iState = aInitState; + } + } + +void RDbTransaction::PrepareXLockL( const CDbObject& aObject ) +// +// prepare to acquire an exclusive lock +// if any other holder has a lock this fails +// + { + THolder h = aObject.Context(); + switch ( iLockCount ) + { + case 0: // no other holders, acquire the lock + iPrimary.iHolder = h; + iPrimary.iState = 0; // this is not a transaction lock + break; + case 1: // check we are the single Lock holder + if (iPrimary.iHolder != h) + __LEAVE( KErrLocked ); + break; + default: // cannot get XLock + __LEAVE( KErrLocked ); + break; + } + } + +void RDbTransaction::Unlock( RDbNotifier::TEvent aEvent ) +// +// Remove the last lock and signal an event to the Notifier +// + { + __ASSERT( iLockCount == 1 ); + __ASSERT( ( iPrimary.iState & ~ETransactionLock ) == 0 ); + TDbLockType ls = LockState(); + Event( ls == EDbReadLock || ls == EDbXReadLock ? RDbNotifier::EUnlock : aEvent ); + iLockCount = 0; + iAction = iLockState = EDbReadLock; + iUpdaters = 0; + Database().CheckIdle(); + } + +void RDbTransaction::Unlock( RDbTransaction::TLock& aLock ) +// +// Remove a shared lock holder from the list +// + { + __ASSERT( iLockCount > 1 ); + __ASSERT( LockState() == EDbReadLock ); + __ASSERT( ( aLock.iState & ~ETransactionLock ) == 0 ); + aLock = iSharers[--iLockCount - 1]; + } + +RDbTransaction::TLock* RDbTransaction::GetLock( const CDbObject& aObject ) +// +// Test if aObject holds any lock, and return it +// + { + const THolder h = aObject.Context(); + TInt lc = iLockCount; + if ( --lc >= 0 ) + { + if ( iPrimary.iHolder == h ) + return &iPrimary; + if ( lc > 0 ) + { + TLock* const base = iSharers; + TLock* l = base + lc; + do { + if ( ( --l )->iHolder == h ) + return l; + } while ( l > base ); + } + } + return 0; + } + +TBool RDbTransaction::InTransaction( const CDbObject& aObject ) +// +// Test if aObject holds a non-auto transaction +// + { + __INVARIANT; + TLock* lock = GetLock( aObject ); + return lock ? lock->iState & static_cast( ETransactionLock ) : 0; + } + +void RDbTransaction::ReadPrepareL( const CDbObject& aObject ) +// +// Check that aObject can gain a shared read lock and allocate required resources +// + { + __INVARIANT_L; + if ( GetLock( aObject ) == 0 ) + PrepareSLockL( aObject, 0 ); // prepare a S-Lock for the read + else if ( iAction == EDbCompactLock ) // Cannot already hold a compaction lock + __LEAVE( KErrAccessDenied ); + } + +void RDbTransaction::ReadBegin( const CDbObject& aObject ) +// +// Take a read-lock: ReadPrepareL(aObject) _must_ already have been called +// + { + __INVARIANT; + TLock* lock = GetLock( aObject ); + if ( !lock ) + { + ++iLockCount; + lock = GetLock( aObject ); + __ASSERT( lock ); + } + ++lock->iState; + } + +void RDbTransaction::ReadRelease( const CDbObject& aObject ) + { + __INVARIANT; + TLock* lock = GetLock( aObject ); + __ASSERT( lock ); + __ASSERT( ( lock->iState & ~ETransactionLock ) > 0 ); + if ( --lock->iState == 0 ) + { // not transaction-lock + if ( iLockCount > 1 ) + Unlock( *lock ); + else if ( iAction == EDbReadLock ) // no other locks to this client + Unlock( RDbNotifier::EUnlock ); + } + } + +void RDbTransaction::DMLCheckL() +// +// Check that we can open a new rowset +// + { + __INVARIANT_L; + ReadyL(); + if ( iAction > EDbCompactLock ) + __LEAVE( KErrAccessDenied ); + } + +void RDbTransaction::DMLPrepareL( const CDbObject& aObject ) +// +// Check that we can do DML, this should be called immediately prior to DMLBegin +// + { + __INVARIANT_L; + PrepareXLockL( aObject ); + if ( iAction>EDbWriteLock ) + __LEAVE( KErrAccessDenied ); + } + +void RDbTransaction::DMLBegin() +// +// A Rowset begins an update +// + { + __INVARIANT; + __ASSERT( iAction == EDbReadLock || iAction == EDbWriteLock ); + __ASSERT( ( iLockState & EFailed ) == 0 ); + __ASSERT( iLockCount <= 1 ); + if ( iAction == EDbReadLock ) + iAction = EDbWriteLock; + if (iLockState == EDbReadLock ) + iLockState = EDbXReadLock; // escalate lock to exclusive as we are now writing + ++iUpdaters; + iLockCount = 1; + } + +void RDbTransaction::DMLTouch() +// +// This must be called prior to putting DML updates +// + { + __ASSERT( iAction == EDbWriteLock ); + __ASSERT( iUpdaters > 0 ); + TInt ls = iLockState; + if ( ls == EDbXReadLock ) + ls = EDbWriteLock | EFailed; + else + ls |= EFailed; + iLockState = TUint8( ls ); + } + +void RDbTransaction::DMLBeginLC() + { + DMLBegin(); + CleanupStack::PushL( TCleanupItem( DMLAbandon, this ) ); + DMLTouch(); + } + +void RDbTransaction::DMLCommitL() +// +// A rowset has completed an update +// + { + __INVARIANT_L; + __ASSERT( iAction == EDbWriteLock && ( iLockState & EFailed ) ); + TInt updaters = iUpdaters - 1; + if ( updaters == 0 ) + { + if ( ( iPrimary.iState & static_cast( ETransactionLock ) ) == 0 ) + { + __ASSERT( iLockState == ( EDbWriteLock | EFailed ) ); + DoCommitL(); // automatic write-commit, release auto-lock + return; + } + iAction = EDbReadLock; + } + iUpdaters = updaters; + iLockState &= ~EFailed; + } + +void RDbTransaction::DMLRollback() +// +// Rollback a DML operation +// + { + __INVARIANT; + __ASSERT( iAction == EDbWriteLock ); + TInt updates = iUpdaters - 1; + if ( updates == 0 ) + { + if ( ( iPrimary.iState & static_cast( ETransactionLock ) ) == 0 ) + { + __ASSERT( LockState() == EDbWriteLock || LockState() == EDbXReadLock ); + DoRollback(); // automatic rollback now (may panic) + return; + } + iAction = EDbReadLock; + } + iUpdaters = updates; + } + +void RDbTransaction::DMLAbandon( TAny* aPtr ) + { + STATIC_CAST( RDbTransaction*, aPtr )->DMLRollback(); + } + +void RDbTransaction::DDLPrepareL( const CDbObject& aObject ) +// +// Check that we can use the database for ddl and flush out any tables +// should be called before DDLBegin +// + { + __INVARIANT_L; + ReadyL(); + PrepareXLockL( aObject ); + if ( iAction != EDbReadLock || ( IsLocked() && iPrimary.iState != static_cast( ETransactionLock ) ) ) + __LEAVE( KErrAccessDenied ); // Cannot take sole ownership of the database + TInt ls = iLockState; + if ( ls >= EDbWriteLock ) + { // ensure all table data is flushed as they may be "released" + iLockState = TUint8( ls | EFailed ); + Database().FlushL( EDbWriteLock ); + iLockState = TUint8( ls ); + } + } + +void RDbTransaction::DDLBegin() +// +// A DDL object is about to start ops +// + { + __INVARIANT; + __ASSERT( iAction == EDbReadLock ); + __ASSERT( ( iLockState & EFailed ) == 0 ); + __ASSERT( iLockCount <= 1 ); + iLockState = iAction = EDbSchemaLock; + iLockCount = 1; + } + +void RDbTransaction::DDLBeginLC() + { + DDLBegin(); + CleanupStack::PushL( TCleanupItem( DDLAbandon, this ) ); + } + +void RDbTransaction::DDLCommitL() +// +// A DDL incremental object has completed +// + { + __INVARIANT_L; + __ASSERT( iAction == EDbSchemaLock ); + if ( ( iPrimary.iState & static_cast( ETransactionLock ) ) == 0 ) + { + __ASSERT( iLockState == EDbSchemaLock ); + DoCommitL(); // release auto-lock + } + else + iAction = EDbReadLock; + } + +void RDbTransaction::DDLRollback() +// +// Rollback a DDL operation +// + { + __INVARIANT; + __ASSERT( iAction == EDbSchemaLock ); + iLockState |= EFailed; + if ( ( iPrimary.iState & static_cast( ETransactionLock ) ) == 0 ) + { + __ASSERT( iLockState == ( EDbSchemaLock | EFailed ) ); + DoRollback(); // release auto-lock + } + else + iAction = EDbReadLock; + } + +void RDbTransaction::DDLAbandon( TAny* aPtr ) + { + STATIC_CAST( RDbTransaction*, aPtr )->DDLRollback(); + } + +// recovery. Nothing else can be done at the same time as this + +void RDbTransaction::UtilityPrepareL( const CDbObject& aObject ) +// +// Check that we are in a state to run a utility +// + { + __INVARIANT_L; + ReadyL(); + PrepareXLockL( aObject ); + if ( IsLocked() ) // utilities not allowed in user transaction + __LEAVE( KErrAccessDenied ); + } + +void RDbTransaction::UtilityBegin( CDbDatabase::TUtility aType ) +// +// Database Recovery object is about to start +// + { + __INVARIANT; + __ASSERT( !IsLocked() ); + if ( aType == CDbDatabase::ERecover ) + iLockState = iAction = EDbRecoveryLock; + else + iLockState = iAction = EDbCompactLock; + iLockCount = 1; + } + +void RDbTransaction::UtilityCommitL() +// +// Database Recovery has completed +// + { + __INVARIANT_L; + Database().SynchL( LockState() ); + Unlock( iAction == EDbRecoveryLock ? RDbNotifier::ERecover : RDbNotifier::EUnlock ); // release auto-lock + } + +void RDbTransaction::UtilityRollback() + { + __INVARIANT; + Database().Revert( LockState() ); + Unlock( RDbNotifier::EUnlock ); // release auto-lock + } + +CDbNotifier* RDbTransaction::NotifierL() +// +// Only support a single notifier for the database (server multiplexes) +// + { + if ( iNotifier ) + __LEAVE( KErrNotSupported ); + return iNotifier = new( ELeave ) CNotifier( *this ); + } + +void RDbTransaction::Event( RDbNotifier::TEvent aEvent ) +// +// Report an event to the Notifier +// If the lock was less than a write lock, report unlock only: no commit or rollback +// + { + if ( iNotifier ) + iNotifier->Event( aEvent ); + } + +