LCOV - code coverage report
Current view: top level - core - qgsconnectionpool.h (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 90 121 74.4 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :     qgsconnectionpool.h
       3                 :            :     ---------------------
       4                 :            :     begin                : February 2014
       5                 :            :     copyright            : (C) 2014 by Martin Dobias
       6                 :            :     email                : wonder dot sk at gmail dot com
       7                 :            :  ***************************************************************************
       8                 :            :  *                                                                         *
       9                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      10                 :            :  *   it under the terms of the GNU General Public License as published by  *
      11                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      12                 :            :  *   (at your option) any later version.                                   *
      13                 :            :  *                                                                         *
      14                 :            :  ***************************************************************************/
      15                 :            : 
      16                 :            : #ifndef QGSCONNECTIONPOOL_H
      17                 :            : #define QGSCONNECTIONPOOL_H
      18                 :            : 
      19                 :            : #define SIP_NO_FILE
      20                 :            : 
      21                 :            : #include "qgis.h"
      22                 :            : #include "qgsapplication.h"
      23                 :            : #include <QCoreApplication>
      24                 :            : #include <QMap>
      25                 :            : #include <QMutex>
      26                 :            : #include <QSemaphore>
      27                 :            : #include <QStack>
      28                 :            : #include <QTime>
      29                 :            : #include <QTimer>
      30                 :            : #include <QThread>
      31                 :            : 
      32                 :            : 
      33                 :            : #define CONN_POOL_EXPIRATION_TIME           60    // in seconds
      34                 :            : #define CONN_POOL_SPARE_CONNECTIONS          2    // number of spare connections in case all the base connections are used but we have a nested request with the risk of a deadlock
      35                 :            : 
      36                 :            : 
      37                 :            : /**
      38                 :            :  * \ingroup core
      39                 :            :  * \brief Template that stores data related to a connection to a single server or datasource.
      40                 :            :  *
      41                 :            :  * It is assumed that following functions exist:
      42                 :            :  *
      43                 :            :  * - void qgsConnectionPool_ConnectionCreate(QString name, T& c)  ... create a new connection
      44                 :            :  * - void qgsConnectionPool_ConnectionDestroy(T c)                ... destroy the connection
      45                 :            :  * - QString qgsConnectionPool_ConnectionToName(T c)              ... lookup connection's name (path)
      46                 :            :  * - void qgsConnectionPool_InvalidateConnection(T c)             ... flag a connection as invalid
      47                 :            :  * - bool qgsConnectionPool_ConnectionIsValid(T c)                ... return whether a connection is valid
      48                 :            :  *
      49                 :            :  * Because of issues with templates and QObject's signals and slots, this class only provides helper functions for QObject-related
      50                 :            :  * functionality - the place which uses the template is resonsible for:
      51                 :            :  *
      52                 :            :  * - being derived from QObject
      53                 :            :  * - calling initTimer( this ) in constructor
      54                 :            :  * - having handleConnectionExpired() slot that calls onConnectionExpired()
      55                 :            :  * - having startExpirationTimer(), stopExpirationTimer() slots to start/stop the expiration timer
      56                 :            :  *
      57                 :            :  * For an example on how to use the template class, have a look at the implementation in Postgres/SpatiaLite providers.
      58                 :            :  * \note not available in Python bindings
      59                 :            :  */
      60                 :            : template <typename T>
      61                 :            : class QgsConnectionPoolGroup
      62                 :            : {
      63                 :            :   public:
      64                 :            : 
      65                 :         92 :     struct Item
      66                 :            :     {
      67                 :            :       T c;
      68                 :            :       QTime lastUsedTime;
      69                 :            :     };
      70                 :            : 
      71                 :         64 :     QgsConnectionPoolGroup( const QString &ci )
      72                 :         64 :       : connInfo( ci )
      73                 :         64 :       , sem( QgsApplication::instance()->maxConcurrentConnectionsPerPool() + CONN_POOL_SPARE_CONNECTIONS )
      74                 :            :     {
      75                 :         64 :     }
      76                 :            : 
      77                 :         64 :     ~QgsConnectionPoolGroup()
      78                 :            :     {
      79                 :         67 :       for ( const Item &item : std::as_const( conns ) )
      80                 :            :       {
      81                 :          3 :         qgsConnectionPool_ConnectionDestroy( item.c );
      82                 :            :       }
      83                 :         64 :     }
      84                 :            : 
      85                 :            :     //! QgsConnectionPoolGroup cannot be copied
      86                 :            :     QgsConnectionPoolGroup( const QgsConnectionPoolGroup &other ) = delete;
      87                 :            :     //! QgsConnectionPoolGroup cannot be copied
      88                 :            :     QgsConnectionPoolGroup &operator=( const QgsConnectionPoolGroup &other ) = delete;
      89                 :            : 
      90                 :            :     /**
      91                 :            :      * Try to acquire a connection for a maximum of \a timeout milliseconds.
      92                 :            :      * If \a timeout is a negative value the calling thread will be blocked
      93                 :            :      * until a connection becomes available. This is the default behavior.
      94                 :            :      *
      95                 :            :      * \returns initialized connection or NULLPTR if unsuccessful
      96                 :            :      */
      97                 :         92 :     T acquire( int timeout, bool requestMayBeNested )
      98                 :            :     {
      99                 :         92 :       const int requiredFreeConnectionCount = requestMayBeNested ? 1 : 3;
     100                 :            :       // we are going to acquire a resource - if no resource is available, we will block here
     101                 :         92 :       if ( timeout >= 0 )
     102                 :            :       {
     103                 :          0 :         if ( !sem.tryAcquire( requiredFreeConnectionCount, timeout ) )
     104                 :          0 :           return nullptr;
     105                 :          0 :       }
     106                 :            :       else
     107                 :            :       {
     108                 :            :         // we should still be able to use tryAcquire with a negative timeout here, but
     109                 :            :         // tryAcquire is broken on Qt > 5.8 with negative timeouts - see
     110                 :            :         // https://bugreports.qt.io/browse/QTBUG-64413
     111                 :            :         // https://lists.osgeo.org/pipermail/qgis-developer/2017-November/050456.html
     112                 :         92 :         sem.acquire( requiredFreeConnectionCount );
     113                 :            :       }
     114                 :         92 :       sem.release( requiredFreeConnectionCount - 1 );
     115                 :            : 
     116                 :            :       // quick (preferred) way - use cached connection
     117                 :            :       {
     118                 :         92 :         QMutexLocker locker( &connMutex );
     119                 :            : 
     120                 :         92 :         if ( !conns.isEmpty() )
     121                 :            :         {
     122                 :          5 :           Item i = conns.pop();
     123                 :          5 :           if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
     124                 :            :           {
     125                 :          0 :             qgsConnectionPool_ConnectionDestroy( i.c );
     126                 :          0 :             qgsConnectionPool_ConnectionCreate( connInfo, i.c );
     127                 :          0 :           }
     128                 :            : 
     129                 :            : 
     130                 :            :           // no need to run if nothing can expire
     131                 :          5 :           if ( conns.isEmpty() )
     132                 :            :           {
     133                 :            :             // will call the slot directly or queue the call (if the object lives in a different thread)
     134                 :          5 :             QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
     135                 :          5 :           }
     136                 :            : 
     137                 :          5 :           acquiredConns.append( i.c );
     138                 :            : 
     139                 :          5 :           return i.c;
     140                 :            :         }
     141                 :         92 :       }
     142                 :            : 
     143                 :            :       T c;
     144                 :         87 :       qgsConnectionPool_ConnectionCreate( connInfo, c );
     145                 :         87 :       if ( !c )
     146                 :            :       {
     147                 :            :         // we didn't get connection for some reason, so release the lock
     148                 :          0 :         sem.release();
     149                 :          0 :         return nullptr;
     150                 :            :       }
     151                 :            : 
     152                 :         87 :       connMutex.lock();
     153                 :         87 :       acquiredConns.append( c );
     154                 :         87 :       connMutex.unlock();
     155                 :         87 :       return c;
     156                 :         92 :     }
     157                 :            : 
     158                 :         92 :     void release( T conn )
     159                 :            :     {
     160                 :         92 :       connMutex.lock();
     161                 :         92 :       acquiredConns.removeAll( conn );
     162                 :         92 :       if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
     163                 :            :       {
     164                 :          0 :         qgsConnectionPool_ConnectionDestroy( conn );
     165                 :          0 :       }
     166                 :            :       else
     167                 :            :       {
     168                 :         92 :         Item i;
     169                 :         92 :         i.c = conn;
     170                 :         92 :         i.lastUsedTime = QTime::currentTime();
     171                 :         92 :         conns.push( i );
     172                 :            : 
     173                 :         92 :         if ( !expirationTimer->isActive() )
     174                 :            :         {
     175                 :            :           // will call the slot directly or queue the call (if the object lives in a different thread)
     176                 :         69 :           QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
     177                 :         69 :         }
     178                 :            :       }
     179                 :            : 
     180                 :         92 :       connMutex.unlock();
     181                 :            : 
     182                 :         92 :       sem.release(); // this can unlock a thread waiting in acquire()
     183                 :         92 :     }
     184                 :            : 
     185                 :        241 :     void invalidateConnections()
     186                 :            :     {
     187                 :        241 :       connMutex.lock();
     188                 :        325 :       for ( const Item &i : std::as_const( conns ) )
     189                 :            :       {
     190                 :         84 :         qgsConnectionPool_ConnectionDestroy( i.c );
     191                 :            :       }
     192                 :        241 :       conns.clear();
     193                 :        241 :       for ( T c : std::as_const( acquiredConns ) )
     194                 :          0 :         qgsConnectionPool_InvalidateConnection( c );
     195                 :        241 :       connMutex.unlock();
     196                 :        241 :     }
     197                 :            : 
     198                 :            :   protected:
     199                 :            : 
     200                 :         64 :     void initTimer( QObject *parent )
     201                 :            :     {
     202                 :         64 :       expirationTimer = new QTimer( parent );
     203                 :         64 :       expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
     204                 :         64 :       QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
     205                 :            : 
     206                 :            :       // just to make sure the object belongs to main thread and thus will get events
     207                 :         64 :       if ( qApp )
     208                 :         64 :         parent->moveToThread( qApp->thread() );
     209                 :         64 :     }
     210                 :            : 
     211                 :          0 :     void onConnectionExpired()
     212                 :            :     {
     213                 :          0 :       connMutex.lock();
     214                 :            : 
     215                 :          0 :       QTime now = QTime::currentTime();
     216                 :            : 
     217                 :            :       // what connections have expired?
     218                 :          0 :       QList<int> toDelete;
     219                 :          0 :       for ( int i = 0; i < conns.count(); ++i )
     220                 :            :       {
     221                 :          0 :         if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
     222                 :          0 :           toDelete.append( i );
     223                 :          0 :       }
     224                 :            : 
     225                 :            :       // delete expired connections
     226                 :          0 :       for ( int j = toDelete.count() - 1; j >= 0; --j )
     227                 :            :       {
     228                 :          0 :         int index = toDelete[j];
     229                 :          0 :         qgsConnectionPool_ConnectionDestroy( conns[index].c );
     230                 :          0 :         conns.remove( index );
     231                 :          0 :       }
     232                 :            : 
     233                 :          0 :       if ( conns.isEmpty() )
     234                 :          0 :         expirationTimer->stop();
     235                 :            : 
     236                 :          0 :       connMutex.unlock();
     237                 :          0 :     }
     238                 :            : 
     239                 :            :   protected:
     240                 :            : 
     241                 :            :     QString connInfo;
     242                 :            :     QStack<Item> conns;
     243                 :            :     QList<T> acquiredConns;
     244                 :            :     QMutex connMutex;
     245                 :            :     QSemaphore sem;
     246                 :         64 :     QTimer *expirationTimer = nullptr;
     247                 :            : 
     248                 :            : };
     249                 :            : 
     250                 :            : 
     251                 :            : /**
     252                 :            :  * \ingroup core
     253                 :            :  * \brief Template class responsible for keeping a pool of open connections.
     254                 :            :  *
     255                 :            :  * This is desired to avoid the overhead of creation of new connection every time.
     256                 :            :  *
     257                 :            :  * The methods are thread safe.
     258                 :            :  *
     259                 :            :  * The connection pool has a limit on maximum number of concurrent connections
     260                 :            :  * (per server), once the limit is reached, the acquireConnection() function
     261                 :            :  * will block. All connections that have been acquired must be then released
     262                 :            :  * with releaseConnection() function.
     263                 :            :  *
     264                 :            :  * When the connections are not used for some time, they will get closed automatically
     265                 :            :  * to save resources.
     266                 :            :  * \note not available in Python bindings
     267                 :            :  */
     268                 :            : template <typename T, typename T_Group>
     269                 :          1 : class QgsConnectionPool
     270                 :            : {
     271                 :            :   public:
     272                 :            : 
     273                 :            :     typedef QMap<QString, T_Group *> T_Groups;
     274                 :            : 
     275                 :          1 :     virtual ~QgsConnectionPool()
     276                 :          1 :     {
     277                 :          1 :       mMutex.lock();
     278                 :          4 :       for ( T_Group *group : std::as_const( mGroups ) )
     279                 :            :       {
     280                 :          3 :         delete group;
     281                 :            :       }
     282                 :          1 :       mGroups.clear();
     283                 :          1 :       mMutex.unlock();
     284                 :          1 :     }
     285                 :            : 
     286                 :            :     /**
     287                 :            :      * Try to acquire a connection for a maximum of \a timeout milliseconds.
     288                 :            :      * If \a timeout is a negative value the calling thread will be blocked
     289                 :            :      * until a connection becomes available. This is the default behavior.
     290                 :            :      *
     291                 :            :      * \returns initialized connection or NULLPTR if unsuccessful
     292                 :            :      */
     293                 :         92 :     T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false )
     294                 :            :     {
     295                 :         92 :       mMutex.lock();
     296                 :         92 :       typename T_Groups::iterator it = mGroups.find( connInfo );
     297                 :         92 :       if ( it == mGroups.end() )
     298                 :            :       {
     299                 :          0 :         it = mGroups.insert( connInfo, new T_Group( connInfo ) );
     300                 :          0 :       }
     301                 :         92 :       T_Group *group = *it;
     302                 :         92 :       mMutex.unlock();
     303                 :            : 
     304                 :         92 :       return group->acquire( timeout, requestMayBeNested );
     305                 :          0 :     }
     306                 :            : 
     307                 :            :     //! Release an existing connection so it will get back into the pool and can be reused
     308                 :         92 :     void releaseConnection( T conn )
     309                 :            :     {
     310                 :         92 :       mMutex.lock();
     311                 :         92 :       typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
     312                 :            :       Q_ASSERT( it != mGroups.end() );
     313                 :         92 :       T_Group *group = *it;
     314                 :         92 :       mMutex.unlock();
     315                 :            : 
     316                 :         92 :       group->release( conn );
     317                 :         92 :     }
     318                 :            : 
     319                 :            :     /**
     320                 :            :      * Invalidates all connections to the specified resource.
     321                 :            :      * The internal state of certain handles (for instance OGR) are altered
     322                 :            :      * when a dataset is modified. Consquently, all open handles need to be
     323                 :            :      * invalidated when such datasets are changed to ensure the handles are
     324                 :            :      * refreshed. See the OGR provider for an example where this is needed.
     325                 :            :      */
     326                 :        305 :     void invalidateConnections( const QString &connInfo )
     327                 :            :     {
     328                 :        305 :       mMutex.lock();
     329                 :        305 :       if ( mGroups.contains( connInfo ) )
     330                 :        241 :         mGroups[connInfo]->invalidateConnections();
     331                 :        305 :       mMutex.unlock();
     332                 :        305 :     }
     333                 :            : 
     334                 :            : 
     335                 :            :   protected:
     336                 :            :     T_Groups mGroups;
     337                 :            :     QMutex mMutex;
     338                 :            : };
     339                 :            : 
     340                 :            : 
     341                 :            : #endif // QGSCONNECTIONPOOL_H

Generated by: LCOV version 1.14