LCOV - code coverage report
Current view: top level - core - qgstaskmanager.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 26 531 4.9 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                           qgstaskmanager.cpp
       3                 :            :                           ------------------
       4                 :            :     begin                : April 2016
       5                 :            :     copyright            : (C) 2016 by Nyall Dawson
       6                 :            :     email                : nyall dot dawson at gmail dot com
       7                 :            :  ***************************************************************************/
       8                 :            : 
       9                 :            : /***************************************************************************
      10                 :            :  *                                                                         *
      11                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      12                 :            :  *   it under the terms of the GNU General Public License as published by  *
      13                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      14                 :            :  *   (at your option) any later version.                                   *
      15                 :            :  *                                                                         *
      16                 :            :  ***************************************************************************/
      17                 :            : 
      18                 :            : #include "qgstaskmanager.h"
      19                 :            : #include "qgsproject.h"
      20                 :            : #include "qgsmaplayerlistutils.h"
      21                 :            : #include <mutex>
      22                 :            : #include <QtConcurrentRun>
      23                 :            : 
      24                 :            : 
      25                 :            : //
      26                 :            : // QgsTask
      27                 :            : //
      28                 :            : 
      29                 :          0 : QgsTask::QgsTask( const QString &name, Flags flags )
      30                 :          0 :   : mFlags( flags )
      31                 :          0 :   , mDescription( name )
      32                 :          0 :   , mNotStartedMutex( 1 )
      33                 :          0 : {
      34                 :          0 :   mNotStartedMutex.acquire();
      35                 :          0 : }
      36                 :            : 
      37                 :          0 : QgsTask::~QgsTask()
      38                 :          0 : {
      39                 :            :   Q_ASSERT_X( mStatus != Running, "delete", QStringLiteral( "status was %1" ).arg( mStatus ).toLatin1() );
      40                 :            :   // even here we are not sure that task start method has ended
      41                 :          0 :   mNotFinishedMutex.lock();
      42                 :          0 :   const auto constMSubTasks = mSubTasks;
      43                 :          0 :   for ( const SubTask &subTask : constMSubTasks )
      44                 :            :   {
      45                 :          0 :     delete subTask.task;
      46                 :            :   }
      47                 :          0 :   mNotFinishedMutex.unlock();
      48                 :          0 :   mNotStartedMutex.release();
      49                 :          0 : }
      50                 :            : 
      51                 :          0 : void QgsTask::setDescription( const QString &description )
      52                 :            : {
      53                 :          0 :   mDescription = description;
      54                 :          0 : }
      55                 :            : 
      56                 :          0 : qint64 QgsTask::elapsedTime() const
      57                 :            : {
      58                 :          0 :   return mElapsedTime.elapsed();
      59                 :            : }
      60                 :            : 
      61                 :          0 : void QgsTask::start()
      62                 :            : {
      63                 :          0 :   QMutexLocker locker( &mNotFinishedMutex );
      64                 :          0 :   mNotStartedMutex.release();
      65                 :          0 :   mStartCount++;
      66                 :            :   Q_ASSERT( mStartCount == 1 );
      67                 :            : 
      68                 :          0 :   if ( mStatus != Queued )
      69                 :          0 :     return;
      70                 :            : 
      71                 :          0 :   mStatus = Running;
      72                 :          0 :   mOverallStatus = Running;
      73                 :          0 :   mElapsedTime.start();
      74                 :            : 
      75                 :          0 :   emit statusChanged( Running );
      76                 :          0 :   emit begun();
      77                 :            : 
      78                 :            :   // force initial emission of progressChanged, but respect if task has had initial progress manually set
      79                 :          0 :   setProgress( mProgress );
      80                 :            : 
      81                 :          0 :   if ( run() )
      82                 :            :   {
      83                 :          0 :     completed();
      84                 :          0 :   }
      85                 :            :   else
      86                 :            :   {
      87                 :          0 :     terminated();
      88                 :            :   }
      89                 :          0 : }
      90                 :            : 
      91                 :          0 : void QgsTask::cancel()
      92                 :            : {
      93                 :          0 :   if ( mOverallStatus == Complete || mOverallStatus == Terminated )
      94                 :          0 :     return;
      95                 :            : 
      96                 :          0 :   mShouldTerminateMutex.lock();
      97                 :          0 :   mShouldTerminate = true;
      98                 :          0 :   mShouldTerminateMutex.unlock();
      99                 :          0 :   if ( mStatus == Queued || mStatus == OnHold )
     100                 :            :   {
     101                 :            :     // immediately terminate unstarted jobs
     102                 :          0 :     terminated();
     103                 :          0 :     mNotStartedMutex.release();
     104                 :          0 :   }
     105                 :            : 
     106                 :          0 :   if ( mStatus == Terminated )
     107                 :            :   {
     108                 :          0 :     processSubTasksForTermination();
     109                 :          0 :   }
     110                 :            : 
     111                 :          0 :   const auto constMSubTasks = mSubTasks;
     112                 :          0 :   for ( const SubTask &subTask : constMSubTasks )
     113                 :            :   {
     114                 :          0 :     subTask.task->cancel();
     115                 :            :   }
     116                 :          0 : }
     117                 :            : 
     118                 :          0 : bool QgsTask::isCanceled() const
     119                 :            : {
     120                 :          0 :   QMutexLocker locker( &mShouldTerminateMutex );
     121                 :          0 :   return mShouldTerminate;
     122                 :          0 : }
     123                 :            : 
     124                 :          0 : void QgsTask::hold()
     125                 :            : {
     126                 :          0 :   if ( mStatus == Queued )
     127                 :            :   {
     128                 :          0 :     mStatus = OnHold;
     129                 :          0 :     processSubTasksForHold();
     130                 :          0 :   }
     131                 :            : 
     132                 :          0 :   const auto constMSubTasks = mSubTasks;
     133                 :          0 :   for ( const SubTask &subTask : constMSubTasks )
     134                 :            :   {
     135                 :          0 :     subTask.task->hold();
     136                 :            :   }
     137                 :          0 : }
     138                 :            : 
     139                 :          0 : void QgsTask::unhold()
     140                 :            : {
     141                 :          0 :   if ( mStatus == OnHold )
     142                 :            :   {
     143                 :          0 :     mStatus = Queued;
     144                 :          0 :     mOverallStatus = Queued;
     145                 :          0 :     emit statusChanged( Queued );
     146                 :          0 :   }
     147                 :            : 
     148                 :          0 :   const auto constMSubTasks = mSubTasks;
     149                 :          0 :   for ( const SubTask &subTask : constMSubTasks )
     150                 :            :   {
     151                 :          0 :     subTask.task->unhold();
     152                 :            :   }
     153                 :          0 : }
     154                 :            : 
     155                 :          0 : void QgsTask::addSubTask( QgsTask *subTask, const QgsTaskList &dependencies,
     156                 :            :                           SubTaskDependency subTaskDependency )
     157                 :            : {
     158                 :          0 :   mSubTasks << SubTask( subTask, dependencies, subTaskDependency );
     159                 :          0 :   connect( subTask, &QgsTask::progressChanged, this, [ = ] { setProgress( mProgress ); } );
     160                 :          0 :   connect( subTask, &QgsTask::statusChanged, this, &QgsTask::subTaskStatusChanged );
     161                 :          0 : }
     162                 :            : 
     163                 :          0 : QList<QgsMapLayer *> QgsTask::dependentLayers() const
     164                 :            : {
     165                 :          0 :   return _qgis_listQPointerToRaw( mDependentLayers );
     166                 :            : }
     167                 :            : 
     168                 :          0 : bool QgsTask::waitForFinished( int timeout )
     169                 :            : {
     170                 :            :   // We wait the task to be started
     171                 :          0 :   mNotStartedMutex.acquire();
     172                 :            : 
     173                 :          0 :   bool rv = true;
     174                 :          0 :   if ( mOverallStatus == Complete || mOverallStatus == Terminated )
     175                 :            :   {
     176                 :          0 :     rv = true;
     177                 :          0 :   }
     178                 :            :   else
     179                 :            :   {
     180                 :          0 :     if ( timeout == 0 )
     181                 :          0 :       timeout = std::numeric_limits< int >::max();
     182                 :          0 :     if ( mNotFinishedMutex.tryLock( timeout ) )
     183                 :            :     {
     184                 :          0 :       mNotFinishedMutex.unlock();
     185                 :          0 :       QCoreApplication::sendPostedEvents( this );
     186                 :          0 :       rv = true;
     187                 :          0 :     }
     188                 :            :     else
     189                 :            :     {
     190                 :          0 :       rv = false;
     191                 :            :     }
     192                 :            :   }
     193                 :          0 :   return rv;
     194                 :            : }
     195                 :            : 
     196                 :          0 : void QgsTask::setDependentLayers( const QList< QgsMapLayer * > &dependentLayers )
     197                 :            : {
     198                 :          0 :   mDependentLayers = _qgis_listRawToQPointer( dependentLayers );
     199                 :          0 : }
     200                 :            : 
     201                 :          0 : void QgsTask::subTaskStatusChanged( int status )
     202                 :            : {
     203                 :          0 :   QgsTask *subTask = qobject_cast< QgsTask * >( sender() );
     204                 :          0 :   if ( !subTask )
     205                 :          0 :     return;
     206                 :            : 
     207                 :          0 :   if ( status == Running && mStatus == Queued )
     208                 :            :   {
     209                 :          0 :     mOverallStatus = Running;
     210                 :          0 :   }
     211                 :          0 :   else if ( status == Complete && mStatus == Complete )
     212                 :            :   {
     213                 :            :     //check again if all subtasks are complete
     214                 :          0 :     processSubTasksForCompletion();
     215                 :          0 :   }
     216                 :          0 :   else if ( ( status == Complete || status == Terminated ) && mStatus == Terminated )
     217                 :            :   {
     218                 :            :     //check again if all subtasks are terminated
     219                 :          0 :     processSubTasksForTermination();
     220                 :          0 :   }
     221                 :          0 :   else if ( ( status == Complete || status == Terminated || status == OnHold ) && mStatus == OnHold )
     222                 :            :   {
     223                 :          0 :     processSubTasksForHold();
     224                 :          0 :   }
     225                 :          0 :   else if ( status == Terminated )
     226                 :            :   {
     227                 :            :     //uh oh...
     228                 :          0 :     cancel();
     229                 :          0 :   }
     230                 :          0 : }
     231                 :            : 
     232                 :          0 : void QgsTask::setProgress( double progress )
     233                 :            : {
     234                 :          0 :   mProgress = progress;
     235                 :            : 
     236                 :          0 :   if ( !mSubTasks.isEmpty() )
     237                 :            :   {
     238                 :            :     // calculate total progress including subtasks
     239                 :            : 
     240                 :          0 :     double totalProgress = 0.0;
     241                 :          0 :     const auto constMSubTasks = mSubTasks;
     242                 :          0 :     for ( const SubTask &subTask : constMSubTasks )
     243                 :            :     {
     244                 :          0 :       if ( subTask.task->status() == QgsTask::Complete )
     245                 :            :       {
     246                 :          0 :         totalProgress += 100.0;
     247                 :          0 :       }
     248                 :            :       else
     249                 :            :       {
     250                 :          0 :         totalProgress += subTask.task->progress();
     251                 :            :       }
     252                 :            :     }
     253                 :          0 :     progress = ( progress + totalProgress ) / ( mSubTasks.count() + 1 );
     254                 :          0 :   }
     255                 :            : 
     256                 :            :   // avoid flooding with too many events
     257                 :          0 :   double prevProgress = mTotalProgress;
     258                 :          0 :   mTotalProgress = progress;
     259                 :            : 
     260                 :            :   // avoid spamming with too many progressChanged reports
     261                 :          0 :   if ( static_cast< int >( prevProgress * 10 ) != static_cast< int >( mTotalProgress * 10 ) )
     262                 :          0 :     emit progressChanged( progress );
     263                 :          0 : }
     264                 :            : 
     265                 :          0 : void QgsTask::completed()
     266                 :            : {
     267                 :          0 :   mStatus = Complete;
     268                 :          0 :   QMetaObject::invokeMethod( this, "processSubTasksForCompletion" );
     269                 :          0 : }
     270                 :            : 
     271                 :          0 : void QgsTask::processSubTasksForCompletion()
     272                 :            : {
     273                 :          0 :   bool subTasksCompleted = true;
     274                 :          0 :   const auto constMSubTasks = mSubTasks;
     275                 :          0 :   for ( const SubTask &subTask : constMSubTasks )
     276                 :            :   {
     277                 :          0 :     if ( subTask.task->status() != Complete )
     278                 :            :     {
     279                 :          0 :       subTasksCompleted = false;
     280                 :          0 :       break;
     281                 :            :     }
     282                 :            :   }
     283                 :            : 
     284                 :          0 :   if ( mStatus == Complete && subTasksCompleted )
     285                 :            :   {
     286                 :          0 :     mOverallStatus = Complete;
     287                 :            : 
     288                 :          0 :     setProgress( 100.0 );
     289                 :          0 :     emit statusChanged( Complete );
     290                 :          0 :     emit taskCompleted();
     291                 :          0 :   }
     292                 :          0 :   else if ( mStatus == Complete )
     293                 :            :   {
     294                 :            :     // defer completion until all subtasks are complete
     295                 :          0 :     mOverallStatus = Running;
     296                 :          0 :   }
     297                 :          0 : }
     298                 :            : 
     299                 :          0 : void QgsTask::processSubTasksForTermination()
     300                 :            : {
     301                 :          0 :   bool subTasksTerminated = true;
     302                 :          0 :   const auto constMSubTasks = mSubTasks;
     303                 :          0 :   for ( const SubTask &subTask : constMSubTasks )
     304                 :            :   {
     305                 :          0 :     if ( subTask.task->status() != Terminated && subTask.task->status() != Complete )
     306                 :            :     {
     307                 :          0 :       subTasksTerminated = false;
     308                 :          0 :       break;
     309                 :            :     }
     310                 :          0 :   }
     311                 :            : 
     312                 :          0 :   if ( mStatus == Terminated && subTasksTerminated && mOverallStatus != Terminated )
     313                 :            :   {
     314                 :          0 :     mOverallStatus = Terminated;
     315                 :            : 
     316                 :          0 :     emit statusChanged( Terminated );
     317                 :          0 :     emit taskTerminated();
     318                 :          0 :   }
     319                 :          0 :   else if ( mStatus == Terminated && !subTasksTerminated )
     320                 :            :   {
     321                 :            :     // defer termination until all subtasks are terminated (or complete)
     322                 :          0 :     mOverallStatus = Running;
     323                 :          0 :   }
     324                 :          0 : }
     325                 :          0 : 
     326                 :          0 : void QgsTask::processSubTasksForHold()
     327                 :          0 : {
     328                 :          0 :   bool subTasksRunning = false;
     329                 :          0 :   const auto constMSubTasks = mSubTasks;
     330                 :          0 :   for ( const SubTask &subTask : constMSubTasks )
     331                 :            :   {
     332                 :          0 :     if ( subTask.task->status() == Running )
     333                 :            :     {
     334                 :          0 :       subTasksRunning = true;
     335                 :          0 :       break;
     336                 :            :     }
     337                 :            :   }
     338                 :            : 
     339                 :          0 :   if ( mStatus == OnHold && !subTasksRunning && mOverallStatus != OnHold )
     340                 :            :   {
     341                 :          0 :     mOverallStatus = OnHold;
     342                 :          0 :     emit statusChanged( OnHold );
     343                 :          0 :   }
     344                 :          0 :   else if ( mStatus == OnHold && subTasksRunning )
     345                 :            :   {
     346                 :            :     // defer hold until all subtasks finish running
     347                 :          0 :     mOverallStatus = Running;
     348                 :          0 :   }
     349                 :          0 : }
     350                 :            : 
     351                 :          0 : void QgsTask::terminated()
     352                 :            : {
     353                 :          0 :   mStatus = Terminated;
     354                 :          0 :   QMetaObject::invokeMethod( this, "processSubTasksForTermination" );
     355                 :          0 : }
     356                 :            : 
     357                 :            : 
     358                 :            : ///@cond PRIVATE
     359                 :            : 
     360                 :          0 : class QgsTaskRunnableWrapper : public QRunnable
     361                 :            : {
     362                 :            :   public:
     363                 :            : 
     364                 :          0 :     explicit QgsTaskRunnableWrapper( QgsTask *task )
     365                 :          0 :       : mTask( task )
     366                 :          0 :     {
     367                 :          0 :       setAutoDelete( true );
     368                 :          0 :     }
     369                 :            : 
     370                 :          0 :     void run() override
     371                 :            :     {
     372                 :            :       Q_ASSERT( mTask );
     373                 :          0 :       mTask->start();
     374                 :          0 :     }
     375                 :            : 
     376                 :            :   private:
     377                 :            : 
     378                 :            :     QgsTask *mTask = nullptr;
     379                 :            : 
     380                 :            : };
     381                 :            : 
     382                 :            : ///@endcond
     383                 :            : 
     384                 :            : 
     385                 :            : 
     386                 :            : //
     387                 :            : // QgsTaskManager
     388                 :            : //
     389                 :            : 
     390                 :         15 : QgsTaskManager::QgsTaskManager( QObject *parent )
     391                 :          5 :   : QObject( parent )
     392                 :            : #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
     393                 :            :   , mTaskMutex( new QMutex( QMutex::Recursive ) )
     394                 :            : #else
     395                 :          5 :   , mTaskMutex( new QRecursiveMutex() )
     396                 :            : #endif
     397                 :         10 : {
     398                 :            : 
     399                 :          5 : }
     400                 :            : 
     401                 :         10 : QgsTaskManager::~QgsTaskManager()
     402                 :         10 : {
     403                 :            :   //first tell all tasks to cancel
     404                 :          5 :   cancelAll();
     405                 :            : 
     406                 :            :   //then clean them up, including waiting for them to terminate
     407                 :          5 :   mTaskMutex->lock();
     408                 :          5 :   QMap< long, TaskInfo > tasks = mTasks;
     409                 :          5 :   mTasks.detach();
     410                 :          5 :   mTaskMutex->unlock();
     411                 :          5 :   QMap< long, TaskInfo >::const_iterator it = tasks.constBegin();
     412                 :          5 :   for ( ; it != tasks.constEnd(); ++it )
     413                 :            :   {
     414                 :          0 :     cleanupAndDeleteTask( it.value().task );
     415                 :          0 :   }
     416                 :            : 
     417                 :          5 :   delete mTaskMutex;
     418                 :         10 : }
     419                 :            : 
     420                 :          0 : long QgsTaskManager::addTask( QgsTask *task, int priority )
     421                 :            : {
     422                 :          0 :   return addTaskPrivate( task, QgsTaskList(), false, priority );
     423                 :          0 : }
     424                 :            : 
     425                 :          0 : long QgsTaskManager::addTask( const QgsTaskManager::TaskDefinition &definition, int priority )
     426                 :            : {
     427                 :          0 :   return addTaskPrivate( definition.task,
     428                 :          0 :                          definition.dependentTasks,
     429                 :            :                          false,
     430                 :          0 :                          priority );
     431                 :          0 : }
     432                 :            : 
     433                 :            : 
     434                 :          0 : long QgsTaskManager::addTaskPrivate( QgsTask *task, QgsTaskList dependencies, bool isSubTask, int priority )
     435                 :            : {
     436                 :          0 :   if ( !task )
     437                 :          0 :     return 0;
     438                 :            : 
     439                 :          0 :   if ( !mInitialized )
     440                 :            :   {
     441                 :          0 :     mInitialized = true;
     442                 :            :     // defer connection to project until we actually need it -- we don't want to connect to the project instance in the constructor,
     443                 :            :     // cos that forces early creation of QgsProject
     444                 :          0 :     connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList< QgsMapLayer * >& ) > ( &QgsProject::layersWillBeRemoved ),
     445                 :            :              this, &QgsTaskManager::layersWillBeRemoved );
     446                 :          0 :   }
     447                 :            : 
     448                 :          0 :   long taskId = mNextTaskId++;
     449                 :            : 
     450                 :          0 :   mTaskMutex->lock();
     451                 :          0 :   mTasks.insert( taskId, TaskInfo( task, priority ) );
     452                 :          0 :   if ( isSubTask )
     453                 :            :   {
     454                 :          0 :     mSubTasks << task;
     455                 :          0 :   }
     456                 :            :   else
     457                 :            :   {
     458                 :          0 :     mParentTasks << task;
     459                 :            :   }
     460                 :          0 :   if ( !task->dependentLayers().isEmpty() )
     461                 :          0 :     mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
     462                 :          0 :   mTaskMutex->unlock();
     463                 :            : 
     464                 :          0 :   connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
     465                 :          0 :   if ( !isSubTask )
     466                 :            :   {
     467                 :          0 :     connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
     468                 :          0 :   }
     469                 :            : 
     470                 :            :   // add all subtasks, must be done before dependency resolution
     471                 :          0 :   for ( const QgsTask::SubTask &subTask : std::as_const( task->mSubTasks ) )
     472                 :            :   {
     473                 :          0 :     switch ( subTask.dependency )
     474                 :            :     {
     475                 :            :       case QgsTask::ParentDependsOnSubTask:
     476                 :          0 :         dependencies << subTask.task;
     477                 :          0 :         break;
     478                 :            : 
     479                 :            :       case QgsTask::SubTaskIndependent:
     480                 :            :         //nothing
     481                 :          0 :         break;
     482                 :            :     }
     483                 :            :     //recursively add sub tasks
     484                 :          0 :     addTaskPrivate( subTask.task, subTask.dependencies, true, priority );
     485                 :            :   }
     486                 :            : 
     487                 :          0 :   if ( !dependencies.isEmpty() )
     488                 :            :   {
     489                 :          0 :     mTaskDependencies.insert( taskId, dependencies );
     490                 :          0 :   }
     491                 :            : 
     492                 :          0 :   if ( hasCircularDependencies( taskId ) )
     493                 :            :   {
     494                 :          0 :     task->cancel();
     495                 :          0 :   }
     496                 :            : 
     497                 :          0 :   if ( !isSubTask )
     498                 :            :   {
     499                 :          0 :     emit taskAdded( taskId );
     500                 :          0 :     processQueue();
     501                 :          0 :   }
     502                 :            : 
     503                 :          0 :   return taskId;
     504                 :          0 : }
     505                 :            : 
     506                 :          0 : QgsTask *QgsTaskManager::task( long id ) const
     507                 :            : {
     508                 :          0 :   QMutexLocker ml( mTaskMutex );
     509                 :          0 :   QgsTask *t = nullptr;
     510                 :          0 :   if ( mTasks.contains( id ) )
     511                 :          0 :     t = mTasks.value( id ).task;
     512                 :          0 :   return t;
     513                 :          0 : }
     514                 :            : 
     515                 :          0 : QList<QgsTask *> QgsTaskManager::tasks() const
     516                 :            : {
     517                 :          0 :   QMutexLocker ml( mTaskMutex );
     518                 :          0 :   return qgis::setToList( mParentTasks );
     519                 :          0 : }
     520                 :            : 
     521                 :          0 : int QgsTaskManager::count() const
     522                 :            : {
     523                 :          0 :   QMutexLocker ml( mTaskMutex );
     524                 :          0 :   return mParentTasks.count();
     525                 :          0 : }
     526                 :            : 
     527                 :          0 : long QgsTaskManager::taskId( QgsTask *task ) const
     528                 :            : {
     529                 :          0 :   if ( !task )
     530                 :          0 :     return -1;
     531                 :            : 
     532                 :          0 :   QMutexLocker ml( mTaskMutex );
     533                 :          0 :   QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
     534                 :          0 :   for ( ; it != mTasks.constEnd(); ++it )
     535                 :            :   {
     536                 :          0 :     if ( it.value().task == task )
     537                 :            :     {
     538                 :          0 :       return it.key();
     539                 :            :     }
     540                 :          0 :   }
     541                 :          0 :   return -1;
     542                 :          0 : }
     543                 :            : 
     544                 :          5 : void QgsTaskManager::cancelAll()
     545                 :            : {
     546                 :          5 :   mTaskMutex->lock();
     547                 :          5 :   QSet< QgsTask * > parents = mParentTasks;
     548                 :          5 :   parents.detach();
     549                 :          5 :   mTaskMutex->unlock();
     550                 :            : 
     551                 :          5 :   const auto constParents = parents;
     552                 :          5 :   for ( QgsTask *task : constParents )
     553                 :            :   {
     554                 :          0 :     task->cancel();
     555                 :            :   }
     556                 :          5 : }
     557                 :            : 
     558                 :          0 : bool QgsTaskManager::dependenciesSatisfied( long taskId ) const
     559                 :            : {
     560                 :          0 :   mTaskMutex->lock();
     561                 :          0 :   QMap< long, QgsTaskList > dependencies = mTaskDependencies;
     562                 :          0 :   dependencies.detach();
     563                 :          0 :   mTaskMutex->unlock();
     564                 :            : 
     565                 :          0 :   if ( !dependencies.contains( taskId ) )
     566                 :          0 :     return true;
     567                 :            : 
     568                 :          0 :   const auto constValue = dependencies.value( taskId );
     569                 :          0 :   for ( QgsTask *task : constValue )
     570                 :            :   {
     571                 :          0 :     if ( task->status() != QgsTask::Complete )
     572                 :          0 :       return false;
     573                 :            :   }
     574                 :            : 
     575                 :          0 :   return true;
     576                 :          0 : }
     577                 :            : 
     578                 :          0 : QSet<long> QgsTaskManager::dependencies( long taskId ) const
     579                 :            : {
     580                 :          0 :   QSet<long> results;
     581                 :          0 :   if ( resolveDependencies( taskId, taskId, results ) )
     582                 :          0 :     return results;
     583                 :            :   else
     584                 :          0 :     return QSet<long>();
     585                 :          0 : }
     586                 :            : 
     587                 :          0 : bool QgsTaskManager::resolveDependencies( long firstTaskId, long currentTaskId, QSet<long> &results ) const
     588                 :            : {
     589                 :          0 :   mTaskMutex->lock();
     590                 :          0 :   QMap< long, QgsTaskList > dependencies = mTaskDependencies;
     591                 :          0 :   dependencies.detach();
     592                 :          0 :   mTaskMutex->unlock();
     593                 :            : 
     594                 :          0 :   if ( !dependencies.contains( currentTaskId ) )
     595                 :          0 :     return true;
     596                 :            : 
     597                 :          0 :   const auto constValue = dependencies.value( currentTaskId );
     598                 :          5 :   for ( QgsTask *task : constValue )
     599                 :            :   {
     600                 :          0 :     long dependentTaskId = taskId( task );
     601                 :          0 :     if ( dependentTaskId >= 0 )
     602                 :            :     {
     603                 :          0 :       if ( dependentTaskId == firstTaskId )
     604                 :            :         // circular
     605                 :          0 :         return false;
     606                 :            : 
     607                 :            :       //add task as dependent
     608                 :          0 :       results.insert( dependentTaskId );
     609                 :            :       //plus all its other dependencies
     610                 :          0 :       QSet< long > newTaskDeps;
     611                 :          5 :       if ( !resolveDependencies( firstTaskId, dependentTaskId, newTaskDeps ) )
     612                 :          0 :         return false;
     613                 :            : 
     614                 :          0 :       if ( newTaskDeps.contains( firstTaskId ) )
     615                 :            :       {
     616                 :            :         // circular
     617                 :          0 :         return false;
     618                 :            :       }
     619                 :            : 
     620                 :          0 :       results.unite( newTaskDeps );
     621                 :          0 :     }
     622                 :            :   }
     623                 :            : 
     624                 :          0 :   return true;
     625                 :          0 : }
     626                 :            : 
     627                 :          0 : bool QgsTaskManager::hasCircularDependencies( long taskId ) const
     628                 :            : {
     629                 :          0 :   QSet< long > d;
     630                 :          0 :   return !resolveDependencies( taskId, taskId, d );
     631                 :          0 : }
     632                 :            : 
     633                 :          0 : QList<QgsMapLayer *> QgsTaskManager::dependentLayers( long taskId ) const
     634                 :            : {
     635                 :          0 :   QMutexLocker ml( mTaskMutex );
     636                 :          0 :   return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
     637                 :          0 : }
     638                 :            : 
     639                 :          0 : QList<QgsTask *> QgsTaskManager::tasksDependentOnLayer( QgsMapLayer *layer ) const
     640                 :            : {
     641                 :          0 :   QMutexLocker ml( mTaskMutex );
     642                 :          0 :   QList< QgsTask * > tasks;
     643                 :          0 :   QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
     644                 :          0 :   for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
     645                 :            :   {
     646                 :          0 :     if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
     647                 :            :     {
     648                 :          0 :       QgsTask *layerTask = task( layerIt.key() );
     649                 :          0 :       if ( layerTask )
     650                 :          0 :         tasks << layerTask;
     651                 :          0 :     }
     652                 :          0 :   }
     653                 :          0 :   return tasks;
     654                 :          0 : }
     655                 :            : 
     656                 :          0 : QList<QgsTask *> QgsTaskManager::activeTasks() const
     657                 :            : {
     658                 :          0 :   QMutexLocker ml( mTaskMutex );
     659                 :          0 :   QSet< QgsTask * > activeTasks = mActiveTasks;
     660                 :          0 :   activeTasks.intersect( mParentTasks );
     661                 :          0 :   return qgis::setToList( activeTasks );
     662                 :          0 : }
     663                 :            : 
     664                 :          0 : int QgsTaskManager::countActiveTasks() const
     665                 :            : {
     666                 :          0 :   QMutexLocker ml( mTaskMutex );
     667                 :          0 :   QSet< QgsTask * > tasks = mActiveTasks;
     668                 :          0 :   return tasks.intersect( mParentTasks ).count();
     669                 :          0 : }
     670                 :            : 
     671                 :          0 : void QgsTaskManager::triggerTask( QgsTask *task )
     672                 :            : {
     673                 :          0 :   if ( task )
     674                 :          0 :     emit taskTriggered( task );
     675                 :          0 : }
     676                 :            : 
     677                 :          0 : void QgsTaskManager::taskProgressChanged( double progress )
     678                 :            : {
     679                 :          0 :   QgsTask *task = qobject_cast< QgsTask * >( sender() );
     680                 :            : 
     681                 :            :   //find ID of task
     682                 :          0 :   long id = taskId( task );
     683                 :          0 :   if ( id < 0 )
     684                 :          0 :     return;
     685                 :            : 
     686                 :          0 :   emit progressChanged( id, progress );
     687                 :            : 
     688                 :          0 :   if ( countActiveTasks() == 1 )
     689                 :            :   {
     690                 :          0 :     emit finalTaskProgressChanged( progress );
     691                 :          0 :   }
     692                 :          0 : }
     693                 :            : 
     694                 :          0 : void QgsTaskManager::taskStatusChanged( int status )
     695                 :            : {
     696                 :          0 :   QgsTask *task = qobject_cast< QgsTask * >( sender() );
     697                 :            : 
     698                 :            :   //find ID of task
     699                 :          0 :   long id = taskId( task );
     700                 :          0 :   if ( id < 0 )
     701                 :          0 :     return;
     702                 :            : 
     703                 :          0 :   mTaskMutex->lock();
     704                 :          0 :   QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
     705                 :          0 :   mTaskMutex->unlock();
     706                 :          0 :   if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
     707                 :            :   {
     708                 :          0 :     delete runnable;
     709                 :          0 :     mTasks[ id ].runnable = nullptr;
     710                 :          0 :   }
     711                 :            : 
     712                 :          0 :   if ( status == QgsTask::Terminated || status == QgsTask::Complete )
     713                 :            :   {
     714                 :          0 :     bool result = status == QgsTask::Complete;
     715                 :          0 :     task->finished( result );
     716                 :          0 :   }
     717                 :            : 
     718                 :          0 :   if ( status == QgsTask::Terminated )
     719                 :            :   {
     720                 :            :     //recursively cancel dependent tasks
     721                 :          0 :     cancelDependentTasks( id );
     722                 :          0 :   }
     723                 :            : 
     724                 :          0 :   mTaskMutex->lock();
     725                 :          0 :   bool isParent = mParentTasks.contains( task );
     726                 :          0 :   mTaskMutex->unlock();
     727                 :          0 :   if ( isParent )
     728                 :            :   {
     729                 :            :     // don't emit status changed for subtasks
     730                 :          0 :     emit statusChanged( id, status );
     731                 :          0 :   }
     732                 :            : 
     733                 :          0 :   processQueue();
     734                 :            : 
     735                 :          0 :   if ( status == QgsTask::Terminated || status == QgsTask::Complete )
     736                 :            :   {
     737                 :          0 :     cleanupAndDeleteTask( task );
     738                 :          0 :   }
     739                 :            : 
     740                 :          0 : }
     741                 :            : 
     742                 :          0 : void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer * > &layers )
     743                 :            : {
     744                 :          0 :   mTaskMutex->lock();
     745                 :            :   // scan through layers to be removed
     746                 :          0 :   QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
     747                 :          0 :   layerDependencies.detach();
     748                 :          0 :   mTaskMutex->unlock();
     749                 :            : 
     750                 :          0 :   const auto constLayers = layers;
     751                 :          0 :   for ( QgsMapLayer *layer : constLayers )
     752                 :            :   {
     753                 :            :     // scan through tasks with layer dependencies
     754                 :          0 :     for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
     755                 :          0 :           it != layerDependencies.constEnd(); ++it )
     756                 :            :     {
     757                 :          0 :       if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
     758                 :            :       {
     759                 :            :         //task not dependent on this layer
     760                 :          0 :         continue;
     761                 :            :       }
     762                 :            : 
     763                 :          0 :       QgsTask *dependentTask = task( it.key() );
     764                 :          0 :       if ( dependentTask && ( dependentTask->status() != QgsTask::Complete && dependentTask->status() != QgsTask::Terminated ) )
     765                 :            :       {
     766                 :            :         // incomplete task is dependent on this layer!
     767                 :          0 :         dependentTask->cancel();
     768                 :          0 :       }
     769                 :          0 :     }
     770                 :            :   }
     771                 :          0 : }
     772                 :            : 
     773                 :            : 
     774                 :          0 : bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
     775                 :            : {
     776                 :          0 :   if ( !task )
     777                 :          0 :     return false;
     778                 :            : 
     779                 :          0 :   long id = taskId( task );
     780                 :          0 :   if ( id < 0 )
     781                 :          0 :     return false;
     782                 :            : 
     783                 :          0 :   QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
     784                 :            : 
     785                 :          0 :   task->disconnect( this );
     786                 :            : 
     787                 :          0 :   mTaskMutex->lock();
     788                 :          0 :   if ( mTaskDependencies.contains( id ) )
     789                 :          0 :     mTaskDependencies.remove( id );
     790                 :          0 :   mTaskMutex->unlock();
     791                 :            : 
     792                 :          0 :   emit taskAboutToBeDeleted( id );
     793                 :            : 
     794                 :          0 :   mTaskMutex->lock();
     795                 :          0 :   bool isParent = mParentTasks.contains( task );
     796                 :          0 :   mParentTasks.remove( task );
     797                 :          0 :   mSubTasks.remove( task );
     798                 :          0 :   mTasks.remove( id );
     799                 :          0 :   mLayerDependencies.remove( id );
     800                 :            : 
     801                 :          0 :   if ( task->status() != QgsTask::Complete && task->status() != QgsTask::Terminated )
     802                 :            :   {
     803                 :          0 :     if ( isParent )
     804                 :            :     {
     805                 :            :       // delete task when it's terminated
     806                 :          0 :       connect( task, &QgsTask::taskCompleted, task, &QgsTask::deleteLater );
     807                 :          0 :       connect( task, &QgsTask::taskTerminated, task, &QgsTask::deleteLater );
     808                 :          0 :     }
     809                 :          0 :     task->cancel();
     810                 :          0 :   }
     811                 :            :   else
     812                 :            :   {
     813                 :          0 :     if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
     814                 :            :     {
     815                 :          0 :       delete runnable;
     816                 :          0 :       mTasks[ id ].runnable = nullptr;
     817                 :          0 :     }
     818                 :            : 
     819                 :          0 :     if ( isParent )
     820                 :            :     {
     821                 :            :       //task already finished, kill it
     822                 :          0 :       task->deleteLater();
     823                 :          0 :     }
     824                 :            :   }
     825                 :            : 
     826                 :            :   // at this stage (hopefully) dependent tasks have been canceled or queued
     827                 :          0 :   for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
     828                 :            :   {
     829                 :          0 :     if ( it.value().contains( task ) )
     830                 :            :     {
     831                 :          0 :       it.value().removeAll( task );
     832                 :          0 :     }
     833                 :          0 :   }
     834                 :          0 :   mTaskMutex->unlock();
     835                 :            : 
     836                 :          0 :   return true;
     837                 :          0 : }
     838                 :            : 
     839                 :          0 : void QgsTaskManager::processQueue()
     840                 :            : {
     841                 :          0 :   int prevActiveCount = countActiveTasks();
     842                 :          0 :   mTaskMutex->lock();
     843                 :          0 :   mActiveTasks.clear();
     844                 :          0 :   for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
     845                 :            :   {
     846                 :          0 :     QgsTask *task = it.value().task;
     847                 :          0 :     if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisfied( it.key() ) && it.value().added.testAndSetRelaxed( 0, 1 ) )
     848                 :            :     {
     849                 :          0 :       it.value().createRunnable();
     850                 :          0 :       QThreadPool::globalInstance()->start( it.value().runnable, it.value().priority );
     851                 :          0 :     }
     852                 :            : 
     853                 :          0 :     if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
     854                 :            :     {
     855                 :          0 :       mActiveTasks << task;
     856                 :          0 :     }
     857                 :          0 :   }
     858                 :            : 
     859                 :          0 :   bool allFinished = mActiveTasks.isEmpty();
     860                 :          0 :   mTaskMutex->unlock();
     861                 :            : 
     862                 :          0 :   if ( allFinished )
     863                 :            :   {
     864                 :          0 :     emit allTasksFinished();
     865                 :          0 :   }
     866                 :            : 
     867                 :          0 :   int newActiveCount = countActiveTasks();
     868                 :          0 :   if ( prevActiveCount != newActiveCount )
     869                 :            :   {
     870                 :          0 :     emit countActiveTasksChanged( newActiveCount );
     871                 :          0 :   }
     872                 :          0 : }
     873                 :            : 
     874                 :          0 : void QgsTaskManager::cancelDependentTasks( long taskId )
     875                 :            : {
     876                 :          0 :   QgsTask *canceledTask = task( taskId );
     877                 :            : 
     878                 :            :   //deep copy
     879                 :          0 :   mTaskMutex->lock();
     880                 :          0 :   QMap< long, QgsTaskList > taskDependencies = mTaskDependencies;
     881                 :          0 :   taskDependencies.detach();
     882                 :          0 :   mTaskMutex->unlock();
     883                 :            : 
     884                 :          0 :   QMap< long, QgsTaskList >::const_iterator it = taskDependencies.constBegin();
     885                 :          0 :   for ( ; it != taskDependencies.constEnd(); ++it )
     886                 :            :   {
     887                 :          0 :     if ( it.value().contains( canceledTask ) )
     888                 :            :     {
     889                 :            :       // found task with this dependency
     890                 :            : 
     891                 :            :       // cancel it - note that this will be recursive, so any tasks dependent
     892                 :            :       // on this one will also be canceled
     893                 :          0 :       QgsTask *dependentTask = task( it.key() );
     894                 :          0 :       if ( dependentTask )
     895                 :          0 :         dependentTask->cancel();
     896                 :          0 :     }
     897                 :          0 :   }
     898                 :          0 : }
     899                 :            : 
     900                 :          0 : QgsTaskManager::TaskInfo::TaskInfo( QgsTask *task, int priority )
     901                 :          0 :   : task( task )
     902                 :          0 :   , added( 0 )
     903                 :          0 :   , priority( priority )
     904                 :          0 : {}
     905                 :            : 
     906                 :          0 : void QgsTaskManager::TaskInfo::createRunnable()
     907                 :            : {
     908                 :            :   Q_ASSERT( !runnable );
     909                 :          0 :   runnable = new QgsTaskRunnableWrapper( task ); // auto deleted
     910                 :          0 : }

Generated by: LCOV version 1.14