LCOV - code coverage report
Current view: top level - core - qgstaskmanager.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 26 533 4.9 %
Date: 2021-03-26 12:19:53 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                 :          5 :   , mTaskMutex( new QMutex( QMutex::Recursive ) )
     393                 :         10 : {
     394                 :            : 
     395                 :          5 : }
     396                 :            : 
     397                 :         10 : QgsTaskManager::~QgsTaskManager()
     398                 :         10 : {
     399                 :            :   //first tell all tasks to cancel
     400                 :          5 :   cancelAll();
     401                 :            : 
     402                 :            :   //then clean them up, including waiting for them to terminate
     403                 :          5 :   mTaskMutex->lock();
     404                 :          5 :   QMap< long, TaskInfo > tasks = mTasks;
     405                 :          5 :   mTasks.detach();
     406                 :          5 :   mTaskMutex->unlock();
     407                 :          5 :   QMap< long, TaskInfo >::const_iterator it = tasks.constBegin();
     408                 :          5 :   for ( ; it != tasks.constEnd(); ++it )
     409                 :            :   {
     410                 :          0 :     cleanupAndDeleteTask( it.value().task );
     411                 :          0 :   }
     412                 :            : 
     413                 :          5 :   delete mTaskMutex;
     414                 :         10 : }
     415                 :            : 
     416                 :          0 : long QgsTaskManager::addTask( QgsTask *task, int priority )
     417                 :            : {
     418                 :          0 :   return addTaskPrivate( task, QgsTaskList(), false, priority );
     419                 :          0 : }
     420                 :            : 
     421                 :          0 : long QgsTaskManager::addTask( const QgsTaskManager::TaskDefinition &definition, int priority )
     422                 :            : {
     423                 :          0 :   return addTaskPrivate( definition.task,
     424                 :          0 :                          definition.dependentTasks,
     425                 :            :                          false,
     426                 :          0 :                          priority );
     427                 :          0 : }
     428                 :            : 
     429                 :            : 
     430                 :          0 : long QgsTaskManager::addTaskPrivate( QgsTask *task, QgsTaskList dependencies, bool isSubTask, int priority )
     431                 :            : {
     432                 :          0 :   if ( !task )
     433                 :          0 :     return 0;
     434                 :            : 
     435                 :          0 :   if ( !mInitialized )
     436                 :            :   {
     437                 :          0 :     mInitialized = true;
     438                 :            :     // defer connection to project until we actually need it -- we don't want to connect to the project instance in the constructor,
     439                 :            :     // cos that forces early creation of QgsProject
     440                 :          0 :     connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList< QgsMapLayer * >& ) > ( &QgsProject::layersWillBeRemoved ),
     441                 :            :              this, &QgsTaskManager::layersWillBeRemoved );
     442                 :          0 :   }
     443                 :            : 
     444                 :          0 :   long taskId = mNextTaskId++;
     445                 :            : 
     446                 :          0 :   mTaskMutex->lock();
     447                 :          0 :   mTasks.insert( taskId, TaskInfo( task, priority ) );
     448                 :          0 :   if ( isSubTask )
     449                 :            :   {
     450                 :          0 :     mSubTasks << task;
     451                 :          0 :   }
     452                 :            :   else
     453                 :            :   {
     454                 :          0 :     mParentTasks << task;
     455                 :            :   }
     456                 :          0 :   if ( !task->dependentLayers().isEmpty() )
     457                 :          0 :     mLayerDependencies.insert( taskId, _qgis_listRawToQPointer( task->dependentLayers() ) );
     458                 :          0 :   mTaskMutex->unlock();
     459                 :            : 
     460                 :          0 :   connect( task, &QgsTask::statusChanged, this, &QgsTaskManager::taskStatusChanged );
     461                 :          0 :   if ( !isSubTask )
     462                 :            :   {
     463                 :          0 :     connect( task, &QgsTask::progressChanged, this, &QgsTaskManager::taskProgressChanged );
     464                 :          0 :   }
     465                 :            : 
     466                 :            :   // add all subtasks, must be done before dependency resolution
     467                 :          0 :   for ( const QgsTask::SubTask &subTask : std::as_const( task->mSubTasks ) )
     468                 :            :   {
     469                 :          0 :     switch ( subTask.dependency )
     470                 :            :     {
     471                 :            :       case QgsTask::ParentDependsOnSubTask:
     472                 :          0 :         dependencies << subTask.task;
     473                 :          0 :         break;
     474                 :            : 
     475                 :            :       case QgsTask::SubTaskIndependent:
     476                 :            :         //nothing
     477                 :          0 :         break;
     478                 :            :     }
     479                 :            :     //recursively add sub tasks
     480                 :          0 :     addTaskPrivate( subTask.task, subTask.dependencies, true, priority );
     481                 :            :   }
     482                 :            : 
     483                 :          0 :   if ( !dependencies.isEmpty() )
     484                 :            :   {
     485                 :          0 :     mTaskDependencies.insert( taskId, dependencies );
     486                 :          0 :   }
     487                 :            : 
     488                 :          0 :   if ( hasCircularDependencies( taskId ) )
     489                 :            :   {
     490                 :          0 :     task->cancel();
     491                 :          0 :   }
     492                 :            : 
     493                 :          0 :   if ( !isSubTask )
     494                 :            :   {
     495                 :          0 :     emit taskAdded( taskId );
     496                 :          0 :     processQueue();
     497                 :          0 :   }
     498                 :            : 
     499                 :          0 :   return taskId;
     500                 :          0 : }
     501                 :            : 
     502                 :          0 : QgsTask *QgsTaskManager::task( long id ) const
     503                 :            : {
     504                 :          0 :   QMutexLocker ml( mTaskMutex );
     505                 :          0 :   QgsTask *t = nullptr;
     506                 :          0 :   if ( mTasks.contains( id ) )
     507                 :          0 :     t = mTasks.value( id ).task;
     508                 :          0 :   return t;
     509                 :          0 : }
     510                 :            : 
     511                 :          0 : QList<QgsTask *> QgsTaskManager::tasks() const
     512                 :            : {
     513                 :          0 :   QMutexLocker ml( mTaskMutex );
     514                 :          0 :   return qgis::setToList( mParentTasks );
     515                 :          0 : }
     516                 :            : 
     517                 :          0 : int QgsTaskManager::count() const
     518                 :            : {
     519                 :          0 :   QMutexLocker ml( mTaskMutex );
     520                 :          0 :   return mParentTasks.count();
     521                 :          0 : }
     522                 :            : 
     523                 :          0 : long QgsTaskManager::taskId( QgsTask *task ) const
     524                 :            : {
     525                 :          0 :   if ( !task )
     526                 :          0 :     return -1;
     527                 :            : 
     528                 :          0 :   QMutexLocker ml( mTaskMutex );
     529                 :          0 :   QMap< long, TaskInfo >::const_iterator it = mTasks.constBegin();
     530                 :          0 :   for ( ; it != mTasks.constEnd(); ++it )
     531                 :            :   {
     532                 :          0 :     if ( it.value().task == task )
     533                 :            :     {
     534                 :          0 :       return it.key();
     535                 :            :     }
     536                 :          0 :   }
     537                 :          0 :   return -1;
     538                 :          0 : }
     539                 :            : 
     540                 :          5 : void QgsTaskManager::cancelAll()
     541                 :            : {
     542                 :          5 :   mTaskMutex->lock();
     543                 :          5 :   QSet< QgsTask * > parents = mParentTasks;
     544                 :          5 :   parents.detach();
     545                 :          5 :   mTaskMutex->unlock();
     546                 :            : 
     547                 :          5 :   const auto constParents = parents;
     548                 :          5 :   for ( QgsTask *task : constParents )
     549                 :            :   {
     550                 :          0 :     task->cancel();
     551                 :            :   }
     552                 :          5 : }
     553                 :            : 
     554                 :          0 : bool QgsTaskManager::dependenciesSatisfied( long taskId ) const
     555                 :            : {
     556                 :          0 :   mTaskMutex->lock();
     557                 :          0 :   QMap< long, QgsTaskList > dependencies = mTaskDependencies;
     558                 :          0 :   dependencies.detach();
     559                 :          0 :   mTaskMutex->unlock();
     560                 :            : 
     561                 :          0 :   if ( !dependencies.contains( taskId ) )
     562                 :          0 :     return true;
     563                 :            : 
     564                 :          0 :   const auto constValue = dependencies.value( taskId );
     565                 :          0 :   for ( QgsTask *task : constValue )
     566                 :            :   {
     567                 :          0 :     if ( task->status() != QgsTask::Complete )
     568                 :          0 :       return false;
     569                 :            :   }
     570                 :            : 
     571                 :          0 :   return true;
     572                 :          0 : }
     573                 :            : 
     574                 :          0 : QSet<long> QgsTaskManager::dependencies( long taskId ) const
     575                 :            : {
     576                 :          0 :   QSet<long> results;
     577                 :          0 :   if ( resolveDependencies( taskId, taskId, results ) )
     578                 :          0 :     return results;
     579                 :            :   else
     580                 :          0 :     return QSet<long>();
     581                 :          0 : }
     582                 :            : 
     583                 :          0 : bool QgsTaskManager::resolveDependencies( long firstTaskId, long currentTaskId, QSet<long> &results ) const
     584                 :            : {
     585                 :          0 :   mTaskMutex->lock();
     586                 :          0 :   QMap< long, QgsTaskList > dependencies = mTaskDependencies;
     587                 :          0 :   dependencies.detach();
     588                 :          0 :   mTaskMutex->unlock();
     589                 :            : 
     590                 :          0 :   if ( !dependencies.contains( currentTaskId ) )
     591                 :          0 :     return true;
     592                 :            : 
     593                 :          0 :   const auto constValue = dependencies.value( currentTaskId );
     594                 :          0 :   for ( QgsTask *task : constValue )
     595                 :          0 :   {
     596                 :          0 :     long dependentTaskId = taskId( task );
     597                 :          0 :     if ( dependentTaskId >= 0 )
     598                 :          5 :     {
     599                 :          0 :       if ( dependentTaskId == firstTaskId )
     600                 :            :         // circular
     601                 :          0 :         return false;
     602                 :            : 
     603                 :            :       //add task as dependent
     604                 :          0 :       results.insert( dependentTaskId );
     605                 :            :       //plus all its other dependencies
     606                 :          0 :       QSet< long > newTaskDeps;
     607                 :          5 :       if ( !resolveDependencies( firstTaskId, dependentTaskId, newTaskDeps ) )
     608                 :          0 :         return false;
     609                 :            : 
     610                 :          0 :       if ( newTaskDeps.contains( firstTaskId ) )
     611                 :            :       {
     612                 :            :         // circular
     613                 :          0 :         return false;
     614                 :            :       }
     615                 :            : 
     616                 :          0 :       results.unite( newTaskDeps );
     617                 :          0 :     }
     618                 :            :   }
     619                 :            : 
     620                 :          0 :   return true;
     621                 :          0 : }
     622                 :            : 
     623                 :          0 : bool QgsTaskManager::hasCircularDependencies( long taskId ) const
     624                 :            : {
     625                 :          0 :   QSet< long > d;
     626                 :          0 :   return !resolveDependencies( taskId, taskId, d );
     627                 :          0 : }
     628                 :            : 
     629                 :          0 : QList<QgsMapLayer *> QgsTaskManager::dependentLayers( long taskId ) const
     630                 :            : {
     631                 :          0 :   QMutexLocker ml( mTaskMutex );
     632                 :          0 :   return _qgis_listQPointerToRaw( mLayerDependencies.value( taskId, QgsWeakMapLayerPointerList() ) );
     633                 :          0 : }
     634                 :            : 
     635                 :          0 : QList<QgsTask *> QgsTaskManager::tasksDependentOnLayer( QgsMapLayer *layer ) const
     636                 :            : {
     637                 :          0 :   QMutexLocker ml( mTaskMutex );
     638                 :          0 :   QList< QgsTask * > tasks;
     639                 :          0 :   QMap< long, QgsWeakMapLayerPointerList >::const_iterator layerIt = mLayerDependencies.constBegin();
     640                 :          0 :   for ( ; layerIt != mLayerDependencies.constEnd(); ++layerIt )
     641                 :            :   {
     642                 :          0 :     if ( _qgis_listQPointerToRaw( layerIt.value() ).contains( layer ) )
     643                 :            :     {
     644                 :          0 :       QgsTask *layerTask = task( layerIt.key() );
     645                 :          0 :       if ( layerTask )
     646                 :          0 :         tasks << layerTask;
     647                 :          0 :     }
     648                 :          0 :   }
     649                 :          0 :   return tasks;
     650                 :          0 : }
     651                 :            : 
     652                 :          0 : QList<QgsTask *> QgsTaskManager::activeTasks() const
     653                 :            : {
     654                 :          0 :   QMutexLocker ml( mTaskMutex );
     655                 :          0 :   QSet< QgsTask * > activeTasks = mActiveTasks;
     656                 :          0 :   activeTasks.intersect( mParentTasks );
     657                 :          0 :   return qgis::setToList( activeTasks );
     658                 :          0 : }
     659                 :            : 
     660                 :          0 : int QgsTaskManager::countActiveTasks() const
     661                 :            : {
     662                 :          0 :   QMutexLocker ml( mTaskMutex );
     663                 :          0 :   QSet< QgsTask * > tasks = mActiveTasks;
     664                 :          0 :   return tasks.intersect( mParentTasks ).count();
     665                 :          0 : }
     666                 :            : 
     667                 :          0 : void QgsTaskManager::triggerTask( QgsTask *task )
     668                 :            : {
     669                 :          0 :   if ( task )
     670                 :          0 :     emit taskTriggered( task );
     671                 :          0 : }
     672                 :            : 
     673                 :          0 : void QgsTaskManager::taskProgressChanged( double progress )
     674                 :            : {
     675                 :          0 :   QgsTask *task = qobject_cast< QgsTask * >( sender() );
     676                 :            : 
     677                 :            :   //find ID of task
     678                 :          0 :   long id = taskId( task );
     679                 :          0 :   if ( id < 0 )
     680                 :          0 :     return;
     681                 :            : 
     682                 :          0 :   emit progressChanged( id, progress );
     683                 :            : 
     684                 :          0 :   if ( countActiveTasks() == 1 )
     685                 :            :   {
     686                 :          0 :     emit finalTaskProgressChanged( progress );
     687                 :          0 :   }
     688                 :          0 : }
     689                 :            : 
     690                 :          0 : void QgsTaskManager::taskStatusChanged( int status )
     691                 :            : {
     692                 :          0 :   QgsTask *task = qobject_cast< QgsTask * >( sender() );
     693                 :            : 
     694                 :            :   //find ID of task
     695                 :          0 :   long id = taskId( task );
     696                 :          0 :   if ( id < 0 )
     697                 :          0 :     return;
     698                 :            : 
     699                 :          0 :   mTaskMutex->lock();
     700                 :          0 :   QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
     701                 :          0 :   mTaskMutex->unlock();
     702                 :          0 :   if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
     703                 :            :   {
     704                 :          0 :     delete runnable;
     705                 :          0 :     mTasks[ id ].runnable = nullptr;
     706                 :          0 :   }
     707                 :            : 
     708                 :          0 :   if ( status == QgsTask::Terminated || status == QgsTask::Complete )
     709                 :            :   {
     710                 :          0 :     bool result = status == QgsTask::Complete;
     711                 :          0 :     task->finished( result );
     712                 :          0 :   }
     713                 :            : 
     714                 :          0 :   if ( status == QgsTask::Terminated )
     715                 :            :   {
     716                 :            :     //recursively cancel dependent tasks
     717                 :          0 :     cancelDependentTasks( id );
     718                 :          0 :   }
     719                 :            : 
     720                 :          0 :   mTaskMutex->lock();
     721                 :          0 :   bool isParent = mParentTasks.contains( task );
     722                 :          0 :   mTaskMutex->unlock();
     723                 :          0 :   if ( isParent )
     724                 :            :   {
     725                 :            :     // don't emit status changed for subtasks
     726                 :          0 :     emit statusChanged( id, status );
     727                 :          0 :   }
     728                 :            : 
     729                 :          0 :   processQueue();
     730                 :            : 
     731                 :          0 :   if ( status == QgsTask::Terminated || status == QgsTask::Complete )
     732                 :            :   {
     733                 :          0 :     cleanupAndDeleteTask( task );
     734                 :          0 :   }
     735                 :            : 
     736                 :          0 : }
     737                 :            : 
     738                 :          0 : void QgsTaskManager::layersWillBeRemoved( const QList< QgsMapLayer * > &layers )
     739                 :            : {
     740                 :          0 :   mTaskMutex->lock();
     741                 :            :   // scan through layers to be removed
     742                 :          0 :   QMap< long, QgsWeakMapLayerPointerList > layerDependencies = mLayerDependencies;
     743                 :          0 :   layerDependencies.detach();
     744                 :          0 :   mTaskMutex->unlock();
     745                 :            : 
     746                 :          0 :   const auto constLayers = layers;
     747                 :          0 :   for ( QgsMapLayer *layer : constLayers )
     748                 :            :   {
     749                 :            :     // scan through tasks with layer dependencies
     750                 :          0 :     for ( QMap< long, QgsWeakMapLayerPointerList >::const_iterator it = layerDependencies.constBegin();
     751                 :          0 :           it != layerDependencies.constEnd(); ++it )
     752                 :            :     {
     753                 :          0 :       if ( !( _qgis_listQPointerToRaw( it.value() ).contains( layer ) ) )
     754                 :            :       {
     755                 :            :         //task not dependent on this layer
     756                 :          0 :         continue;
     757                 :            :       }
     758                 :            : 
     759                 :          0 :       QgsTask *dependentTask = task( it.key() );
     760                 :          0 :       if ( dependentTask && ( dependentTask->status() != QgsTask::Complete && dependentTask->status() != QgsTask::Terminated ) )
     761                 :            :       {
     762                 :            :         // incomplete task is dependent on this layer!
     763                 :          0 :         dependentTask->cancel();
     764                 :          0 :       }
     765                 :          0 :     }
     766                 :            :   }
     767                 :          0 : }
     768                 :            : 
     769                 :            : 
     770                 :          0 : bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
     771                 :            : {
     772                 :          0 :   if ( !task )
     773                 :          0 :     return false;
     774                 :            : 
     775                 :          0 :   long id = taskId( task );
     776                 :          0 :   if ( id < 0 )
     777                 :          0 :     return false;
     778                 :            : 
     779                 :          0 :   QgsTaskRunnableWrapper *runnable = mTasks.value( id ).runnable;
     780                 :            : 
     781                 :          0 :   task->disconnect( this );
     782                 :            : 
     783                 :          0 :   mTaskMutex->lock();
     784                 :          0 :   if ( mTaskDependencies.contains( id ) )
     785                 :          0 :     mTaskDependencies.remove( id );
     786                 :          0 :   mTaskMutex->unlock();
     787                 :            : 
     788                 :          0 :   emit taskAboutToBeDeleted( id );
     789                 :            : 
     790                 :          0 :   mTaskMutex->lock();
     791                 :          0 :   bool isParent = mParentTasks.contains( task );
     792                 :          0 :   mParentTasks.remove( task );
     793                 :          0 :   mSubTasks.remove( task );
     794                 :          0 :   mTasks.remove( id );
     795                 :          0 :   mLayerDependencies.remove( id );
     796                 :            : 
     797                 :          0 :   if ( task->status() != QgsTask::Complete && task->status() != QgsTask::Terminated )
     798                 :            :   {
     799                 :          0 :     if ( isParent )
     800                 :            :     {
     801                 :            :       // delete task when it's terminated
     802                 :          0 :       connect( task, &QgsTask::taskCompleted, task, &QgsTask::deleteLater );
     803                 :          0 :       connect( task, &QgsTask::taskTerminated, task, &QgsTask::deleteLater );
     804                 :          0 :     }
     805                 :          0 :     task->cancel();
     806                 :          0 :   }
     807                 :            :   else
     808                 :            :   {
     809                 :          0 :     if ( runnable && QThreadPool::globalInstance()->tryTake( runnable ) )
     810                 :            :     {
     811                 :          0 :       delete runnable;
     812                 :          0 :       mTasks[ id ].runnable = nullptr;
     813                 :          0 :     }
     814                 :            : 
     815                 :          0 :     if ( isParent )
     816                 :            :     {
     817                 :            :       //task already finished, kill it
     818                 :          0 :       task->deleteLater();
     819                 :          0 :     }
     820                 :            :   }
     821                 :            : 
     822                 :            :   // at this stage (hopefully) dependent tasks have been canceled or queued
     823                 :          0 :   for ( QMap< long, QgsTaskList >::iterator it = mTaskDependencies.begin(); it != mTaskDependencies.end(); ++it )
     824                 :            :   {
     825                 :          0 :     if ( it.value().contains( task ) )
     826                 :            :     {
     827                 :          0 :       it.value().removeAll( task );
     828                 :          0 :     }
     829                 :          0 :   }
     830                 :          0 :   mTaskMutex->unlock();
     831                 :            : 
     832                 :          0 :   return true;
     833                 :          0 : }
     834                 :            : 
     835                 :          0 : void QgsTaskManager::processQueue()
     836                 :            : {
     837                 :          0 :   int prevActiveCount = countActiveTasks();
     838                 :          0 :   mTaskMutex->lock();
     839                 :          0 :   mActiveTasks.clear();
     840                 :          0 :   for ( QMap< long, TaskInfo >::iterator it = mTasks.begin(); it != mTasks.end(); ++it )
     841                 :            :   {
     842                 :          0 :     QgsTask *task = it.value().task;
     843                 :          0 :     if ( task && task->mStatus == QgsTask::Queued && dependenciesSatisfied( it.key() ) && it.value().added.testAndSetRelaxed( 0, 1 ) )
     844                 :            :     {
     845                 :          0 :       it.value().createRunnable();
     846                 :          0 :       QThreadPool::globalInstance()->start( it.value().runnable, it.value().priority );
     847                 :          0 :     }
     848                 :            : 
     849                 :          0 :     if ( task && ( task->mStatus != QgsTask::Complete && task->mStatus != QgsTask::Terminated ) )
     850                 :            :     {
     851                 :          0 :       mActiveTasks << task;
     852                 :          0 :     }
     853                 :          0 :   }
     854                 :            : 
     855                 :          0 :   bool allFinished = mActiveTasks.isEmpty();
     856                 :          0 :   mTaskMutex->unlock();
     857                 :            : 
     858                 :          0 :   if ( allFinished )
     859                 :            :   {
     860                 :          0 :     emit allTasksFinished();
     861                 :          0 :   }
     862                 :            : 
     863                 :          0 :   int newActiveCount = countActiveTasks();
     864                 :          0 :   if ( prevActiveCount != newActiveCount )
     865                 :            :   {
     866                 :          0 :     emit countActiveTasksChanged( newActiveCount );
     867                 :          0 :   }
     868                 :          0 : }
     869                 :            : 
     870                 :          0 : void QgsTaskManager::cancelDependentTasks( long taskId )
     871                 :            : {
     872                 :          0 :   QgsTask *canceledTask = task( taskId );
     873                 :            : 
     874                 :            :   //deep copy
     875                 :          0 :   mTaskMutex->lock();
     876                 :          0 :   QMap< long, QgsTaskList > taskDependencies = mTaskDependencies;
     877                 :          0 :   taskDependencies.detach();
     878                 :          0 :   mTaskMutex->unlock();
     879                 :            : 
     880                 :          0 :   QMap< long, QgsTaskList >::const_iterator it = taskDependencies.constBegin();
     881                 :          0 :   for ( ; it != taskDependencies.constEnd(); ++it )
     882                 :            :   {
     883                 :          0 :     if ( it.value().contains( canceledTask ) )
     884                 :            :     {
     885                 :            :       // found task with this dependency
     886                 :            : 
     887                 :            :       // cancel it - note that this will be recursive, so any tasks dependent
     888                 :            :       // on this one will also be canceled
     889                 :          0 :       QgsTask *dependentTask = task( it.key() );
     890                 :          0 :       if ( dependentTask )
     891                 :          0 :         dependentTask->cancel();
     892                 :          0 :     }
     893                 :          0 :   }
     894                 :          0 : }
     895                 :            : 
     896                 :          0 : QgsTaskManager::TaskInfo::TaskInfo( QgsTask *task, int priority )
     897                 :          0 :   : task( task )
     898                 :          0 :   , added( 0 )
     899                 :          0 :   , priority( priority )
     900                 :          0 : {}
     901                 :            : 
     902                 :          0 : void QgsTaskManager::TaskInfo::createRunnable()
     903                 :            : {
     904                 :            :   Q_ASSERT( !runnable );
     905                 :          0 :   runnable = new QgsTaskRunnableWrapper( task ); // auto deleted
     906                 :          0 : }

Generated by: LCOV version 1.14