App: add class TransactionLocker

To protect some critical transaction from being closed prematurely. It
is currently used to protect transaction of object deletion.
This commit is contained in:
Zheng, Lei
2020-02-04 09:49:49 +08:00
committed by WandererFan
parent 5b3c63c9be
commit b50914923a
4 changed files with 127 additions and 2 deletions

View File

@@ -21,7 +21,9 @@
****************************************************************************/
#include "PreCompiled.h"
#include <Base/Console.h>
#include <Base/Interpreter.h>
#include "Application.h"
#include "Transactions.h"
#include "Document.h"
@@ -31,6 +33,9 @@ FC_LOG_LEVEL_INIT("App",true,true)
using namespace App;
static int _TransactionLock;
static int _TransactionClosed;
AutoTransaction::AutoTransaction(const char *name, bool tmpName) {
auto &app = GetApplication();
if(name && app._activeTransactionGuard>=0) {
@@ -125,7 +130,11 @@ int Application::setActiveTransaction(const char *name, bool persist) {
AutoTransaction::setEnable(false);
return 0;
}
}else{
} else if (_TransactionLock) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG))
FC_WARN("Transaction locked, ignore new transaction '" << name << "'");
return 0;
} else {
FC_LOG("set active transaction '" << name << "'");
_activeTransactionID = 0;
for(auto &v : DocMap)
@@ -156,10 +165,17 @@ void Application::closeActiveTransaction(bool abort, int id) {
return;
}
if(_TransactionLock) {
if(_TransactionClosed >= 0)
_TransactionLock = abort?-1:1;
FC_LOG("pending " << (abort?"abort":"close") << " transaction");
return;
}
FC_LOG("close transaction '" << _activeTransactionName << "' " << abort);
_activeTransactionID = 0;
TransactionSignaller siganller(abort,false);
TransactionSignaller signaller(abort,false);
for(auto &v : DocMap) {
if(v.second->getTransactionID(true) != id)
continue;
@@ -170,3 +186,57 @@ void Application::closeActiveTransaction(bool abort, int id) {
}
}
////////////////////////////////////////////////////////////////////////
TransactionLocker::TransactionLocker(bool lock)
:active(lock)
{
if(lock)
++_TransactionLock;
}
TransactionLocker::~TransactionLocker()
{
if(active) {
try {
activate(false);
return;
} catch (Base::Exception &e) {
e.ReportException();
} catch (Py::Exception &) {
Base::PyException e;
e.ReportException();
} catch (std::exception &e) {
FC_ERR(e.what());
} catch (...) {
}
FC_ERR("Exception when unlocking transaction");
}
}
void TransactionLocker::activate(bool enable)
{
if(active == enable)
return;
active = enable;
if(active) {
++_TransactionLock;
return;
}
if(--_TransactionLock != 0)
return;
if(_TransactionClosed) {
bool abort = (_TransactionClosed<0);
_TransactionClosed = 0;
GetApplication().closeActiveTransaction(abort);
}
}
bool TransactionLocker::isLocked() {
return _TransactionLock > 0;
}

View File

@@ -25,6 +25,8 @@
namespace App {
class Application;
/// Helper class to manager transaction (i.e. undo/redo)
class AppExport AutoTransaction {
private:
@@ -79,6 +81,51 @@ private:
int tid = 0;
};
/** Helper class to lock a transaction from being closed or aborted.
*
* The helper class is used to protect some critical transaction from being
* closed prematurely, e.g. when deleting some object.
*/
class AppExport TransactionLocker {
public:
/** Constructor
* @param lock: whether to activate the lock
*/
TransactionLocker(bool lock=true);
/** Destructor
* Unlock the transaction is this locker is active
*/
~TransactionLocker();
/** Activate or deactivate this locker
* @param enable: whether to activate the locker
*
* An internal counter is used to support recursive locker. When activated,
* the current active transaction cannot be closed or aborted. But the
* closing call (Application::closeActiveTransaction()) will be remembered,
* and performed when the internal lock counter reaches zero.
*/
void activate(bool enable);
/// Check if the locker is active
bool isActive() const {return active;}
/// Check if transaction is being locked
static bool isLocked();
friend class Application;
private:
/// Private new operator to prevent heap allocation
void* operator new(size_t size);
private:
bool active;
};
} // namespace App
#endif // APP_AUTOTRANSACTION_H

View File

@@ -91,6 +91,7 @@ recompute path. Also, it enables more complicated dependencies beyond trees.
#include <QCoreApplication>
#include <QCryptographicHash>
#include "AutoTransaction.h"
#include "Document.h"
#include "Application.h"
#include "DocumentObject.h"
@@ -3764,6 +3765,8 @@ void Document::removeObject(const char* sName)
return;
}
TransactionLocker tlock;
_checkTransaction(pos->second,0,__LINE__);
#if 0
@@ -3865,6 +3868,8 @@ void Document::_removeObject(DocumentObject* pcObject)
return;
}
TransactionLocker tlock;
// TODO Refactoring: share code with Document::removeObject() (2015-09-01, Fat-Zer)
_checkTransaction(pcObject,0,__LINE__);

View File

@@ -1130,6 +1130,9 @@ void StdCmdDelete::activated(int iMsg)
commitCommand();
return;
}
App::TransactionLocker tlock;
Gui::getMainWindow()->setUpdatesEnabled(false);
auto editDoc = Application::Instance->editDocument();
ViewProviderDocumentObject *vpedit = 0;