Skip to content

Commit

Permalink
Make processing algorithms safe to run in threads
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jul 6, 2017
1 parent 0231e77 commit e5b156b
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 5 deletions.
1 change: 0 additions & 1 deletion python/core/processing/qgsprocessingcontext.sip
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ Destination project
.. seealso:: layersToLoadOnCompletion()
%End


QgsFeatureRequest::InvalidGeometryCheck invalidGeometryCheck() const;
%Docstring
Returns the behavior used for checking invalid geometries in input layers.
Expand Down
55 changes: 52 additions & 3 deletions src/core/processing/qgsprocessingalgorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ QVariantMap QgsProcessingAlgorithm::run( const QVariantMap &parameters, QgsProce

bool QgsProcessingAlgorithm::prepare( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
Q_ASSERT_X( QApplication::instance()->thread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::prepare", "prepare() must be called from the same thread as context was created in" );
Q_ASSERT_X( QThread::currentThread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::prepare", "prepare() must be called from the same thread as context was created in" );
Q_ASSERT_X( !mHasPrepared, "QgsProcessingAlgorithm::prepare", "prepare() has already been called for the algorithm instance" );
try
{
Expand All @@ -361,25 +361,74 @@ bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProc
Q_ASSERT_X( mHasPrepared, "QgsProcessingAlgorithm::runPrepared", "prepare() was not called for the algorithm instance" );
Q_ASSERT_X( !mHasExecuted, "QgsProcessingAlgorithm::runPrepared", "runPrepared() was already called for this algorithm instance" );

// Hey kids, let's all be thread safe! It's the fun thing to do!
//
// First, let's see if we're going to run into issues.
QgsProcessingContext *runContext = nullptr;
if ( context.thread() == QThread::currentThread() )
{
// OH. No issues. Seems you're running everything in the same thread, so go about your business. Sorry about
// the intrusion, we're just making sure everything's nice and safe here. We like to keep a clean and tidy neighbourhood,
// you know, for the kids and dogs and all.
runContext = &context;
}
else
{
// HA! I knew things looked a bit suspicious - seems you're running this algorithm in a different thread
// from that which the passed context has an affinity for. That's fine and all, but we need to make sure
// we proceed safely...

// So first we create a temporary local context with affinity for the current thread
mLocalContext.reset( new QgsProcessingContext() );
// copy across everything we can safely do from the passed context
mLocalContext->copyThreadSafeSettings( context );
// and we'll run the actual algorithm processing using the local thread safe context
runContext = mLocalContext.get();
}

try
{
mHasExecuted = processAlgorithm( context, feedback );
mHasExecuted = processAlgorithm( runContext, feedback );

if ( mLocalContext )
{
// ok, time to clean things up. We need to push the temporary context back into
// the thread that the passed context is associated with (we can only push from the
// current thread, so we HAVE to do this here)
mLocalContext->pushToThread( context.thread() );
}
return mHasExecuted;
}
catch ( QgsProcessingException &e )
{
QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), QgsMessageLog::CRITICAL );
feedback->reportError( e.what() );

if ( mLocalContext )
{
// see above!
mLocalContext->pushToThread( context.thread() );
}
return false;
}
}

QVariantMap QgsProcessingAlgorithm::postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
Q_ASSERT_X( QApplication::instance()->thread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the same thread the context was created in" );
Q_ASSERT_X( QThread::currentThread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the same thread the context was created in" );
Q_ASSERT_X( mHasExecuted, "QgsProcessingAlgorithm::postProcess", "runPrepared() was not called for the algorithm instance" );
Q_ASSERT_X( !mHasPostProcessed, "QgsProcessingAlgorithm::postProcess", "postProcess() was already called for this algorithm instance" );

if ( mLocalContext )
{
// algorithm was processed using a temporary thread safe context. So now we need
// to take the results from that temporary context, and smash them into the passed
// context
context.takeResultsFrom( *mLocalContext );
// now get lost, we don't need you anymore
mLocalContext.reset();
}

mHasPostProcessed = true;
try
{
Expand Down
3 changes: 2 additions & 1 deletion src/core/processing/qgsprocessingalgorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
#include "qgis.h"
#include "qgsprocessingparameters.h"
#include "qgsprocessingoutputs.h"
#include "qgsprocessingcontext.h"
#include <QString>
#include <QVariant>
#include <QIcon>

class QgsProcessingProvider;
class QgsProcessingContext;
class QgsProcessingFeedback;
class QgsFeatureSink;

Expand Down Expand Up @@ -539,6 +539,7 @@ class CORE_EXPORT QgsProcessingAlgorithm
bool mHasPrepared = false;
bool mHasExecuted = false;
bool mHasPostProcessed = false;
std::unique_ptr< QgsProcessingContext > mLocalContext;

// friend class to access setProvider() - we do not want this public!
friend class QgsProcessingProvider;
Expand Down

0 comments on commit e5b156b

Please sign in to comment.