QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsconnectionpool.h
Go to the documentation of this file.
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 "qgsfeedback.h"
24 
25 #include <QCoreApplication>
26 #include <QMap>
27 #include <QMutex>
28 #include <QSemaphore>
29 #include <QStack>
30 #include <QTime>
31 #include <QTimer>
32 #include <QThread>
33 #include <QElapsedTimer>
34 
35 #define CONN_POOL_EXPIRATION_TIME 60 // in seconds
36 #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
37 
38 
62 template <typename T>
64 {
65  public:
66 
67  struct Item
68  {
69  T c;
70  QTime lastUsedTime;
71  };
72 
73  QgsConnectionPoolGroup( const QString &ci )
74  : connInfo( ci )
75  , sem( QgsApplication::instance()->maxConcurrentConnectionsPerPool() + CONN_POOL_SPARE_CONNECTIONS )
76  {
77  }
78 
80  {
81  for ( const Item &item : std::as_const( conns ) )
82  {
83  qgsConnectionPool_ConnectionDestroy( item.c );
84  }
85  }
86 
88  QgsConnectionPoolGroup( const QgsConnectionPoolGroup &other ) = delete;
91 
99  T acquire( int timeout, bool requestMayBeNested )
100  {
101  const int requiredFreeConnectionCount = requestMayBeNested ? 1 : 3;
102  // we are going to acquire a resource - if no resource is available, we will block here
103  if ( timeout >= 0 )
104  {
105  if ( !sem.tryAcquire( requiredFreeConnectionCount, timeout ) )
106  return nullptr;
107  }
108  else
109  {
110  // we should still be able to use tryAcquire with a negative timeout here, but
111  // tryAcquire is broken on Qt > 5.8 with negative timeouts - see
112  // https://bugreports.qt.io/browse/QTBUG-64413
113  // https://lists.osgeo.org/pipermail/qgis-developer/2017-November/050456.html
114  sem.acquire( requiredFreeConnectionCount );
115  }
116  sem.release( requiredFreeConnectionCount - 1 );
117 
118  // quick (preferred) way - use cached connection
119  {
120  QMutexLocker locker( &connMutex );
121 
122  if ( !conns.isEmpty() )
123  {
124  Item i = conns.pop();
125  if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
126  {
127  qgsConnectionPool_ConnectionDestroy( i.c );
128  qgsConnectionPool_ConnectionCreate( connInfo, i.c );
129  }
130 
131 
132  // no need to run if nothing can expire
133  if ( conns.isEmpty() )
134  {
135  // will call the slot directly or queue the call (if the object lives in a different thread)
136  QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
137  }
138 
139  acquiredConns.append( i.c );
140 
141  return i.c;
142  }
143  }
144 
145  T c;
146  qgsConnectionPool_ConnectionCreate( connInfo, c );
147  if ( !c )
148  {
149  // we didn't get connection for some reason, so release the lock
150  sem.release();
151  return nullptr;
152  }
153 
154  connMutex.lock();
155  acquiredConns.append( c );
156  connMutex.unlock();
157  return c;
158  }
159 
160  void release( T conn )
161  {
162  connMutex.lock();
163  acquiredConns.removeAll( conn );
164  if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
165  {
166  qgsConnectionPool_ConnectionDestroy( conn );
167  }
168  else
169  {
170  Item i;
171  i.c = conn;
172  i.lastUsedTime = QTime::currentTime();
173  conns.push( i );
174 
175  if ( !expirationTimer->isActive() )
176  {
177  // will call the slot directly or queue the call (if the object lives in a different thread)
178  QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
179  }
180  }
181 
182  connMutex.unlock();
183 
184  sem.release(); // this can unlock a thread waiting in acquire()
185  }
186 
188  {
189  connMutex.lock();
190  for ( const Item &i : std::as_const( conns ) )
191  {
192  qgsConnectionPool_ConnectionDestroy( i.c );
193  }
194  conns.clear();
195  for ( T c : std::as_const( acquiredConns ) )
196  qgsConnectionPool_InvalidateConnection( c );
197  connMutex.unlock();
198  }
199 
200  protected:
201 
202  void initTimer( QObject *parent )
203  {
204  expirationTimer = new QTimer( parent );
205  expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
206  QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
207 
208  // just to make sure the object belongs to main thread and thus will get events
209  if ( qApp )
210  parent->moveToThread( qApp->thread() );
211  }
212 
214  {
215  connMutex.lock();
216 
217  QTime now = QTime::currentTime();
218 
219  // what connections have expired?
220  QList<int> toDelete;
221  for ( int i = 0; i < conns.count(); ++i )
222  {
223  if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
224  toDelete.append( i );
225  }
226 
227  // delete expired connections
228  for ( int j = toDelete.count() - 1; j >= 0; --j )
229  {
230  int index = toDelete[j];
231  qgsConnectionPool_ConnectionDestroy( conns[index].c );
232  conns.remove( index );
233  }
234 
235  if ( conns.isEmpty() )
236  expirationTimer->stop();
237 
238  connMutex.unlock();
239  }
240 
241  protected:
242 
243  QString connInfo;
244  QStack<Item> conns;
245  QList<T> acquiredConns;
246  QMutex connMutex;
247  QSemaphore sem;
248  QTimer *expirationTimer = nullptr;
249 
250 };
251 
252 
270 template <typename T, typename T_Group>
272 {
273  public:
274 
275  typedef QMap<QString, T_Group *> T_Groups;
276 
278  {
279  mMutex.lock();
280  for ( T_Group *group : std::as_const( mGroups ) )
281  {
282  delete group;
283  }
284  mGroups.clear();
285  mMutex.unlock();
286  }
287 
298  T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false, QgsFeedback *feedback = nullptr )
299  {
300  mMutex.lock();
301  typename T_Groups::iterator it = mGroups.find( connInfo );
302  if ( it == mGroups.end() )
303  {
304  it = mGroups.insert( connInfo, new T_Group( connInfo ) );
305  }
306  T_Group *group = *it;
307  mMutex.unlock();
308 
309  if ( feedback )
310  {
311  QElapsedTimer timer;
312  timer.start();
313 
314  while ( !feedback->isCanceled() )
315  {
316  if ( T conn = group->acquire( 300, requestMayBeNested ) )
317  return conn;
318 
319  if ( timeout > 0 && timer.elapsed() >= timeout )
320  return nullptr;
321  }
322  return nullptr;
323  }
324  else
325  {
326  return group->acquire( timeout, requestMayBeNested );
327  }
328  }
329 
331  void releaseConnection( T conn )
332  {
333  mMutex.lock();
334  typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
335  Q_ASSERT( it != mGroups.end() );
336  T_Group *group = *it;
337  mMutex.unlock();
338 
339  group->release( conn );
340  }
341 
349  void invalidateConnections( const QString &connInfo )
350  {
351  mMutex.lock();
352  if ( mGroups.contains( connInfo ) )
353  mGroups[connInfo]->invalidateConnections();
354  mMutex.unlock();
355  }
356 
357 
358  protected:
360  QMutex mMutex;
361 };
362 
363 
364 #endif // QGSCONNECTIONPOOL_H
QgsConnectionPool::~QgsConnectionPool
virtual ~QgsConnectionPool()
Definition: qgsconnectionpool.h:277
QgsConnectionPoolGroup::connMutex
QMutex connMutex
Definition: qgsconnectionpool.h:246
QgsConnectionPoolGroup
Template that stores data related to a connection to a single server or datasource.
Definition: qgsconnectionpool.h:63
QgsConnectionPoolGroup::expirationTimer
QTimer * expirationTimer
Definition: qgsconnectionpool.h:248
QgsConnectionPool::T_Groups
QMap< QString, T_Group * > T_Groups
Definition: qgsconnectionpool.h:275
QgsConnectionPool::invalidateConnections
void invalidateConnections(const QString &connInfo)
Invalidates all connections to the specified resource.
Definition: qgsconnectionpool.h:349
QgsConnectionPoolGroup::release
void release(T conn)
Definition: qgsconnectionpool.h:160
qgis.h
QgsConnectionPoolGroup::sem
QSemaphore sem
Definition: qgsconnectionpool.h:247
QgsConnectionPoolGroup::conns
QStack< Item > conns
Definition: qgsconnectionpool.h:244
QgsConnectionPoolGroup::initTimer
void initTimer(QObject *parent)
Definition: qgsconnectionpool.h:202
qgsapplication.h
QgsConnectionPool
Template class responsible for keeping a pool of open connections.
Definition: qgsconnectionpool.h:271
QgsConnectionPoolGroup::operator=
QgsConnectionPoolGroup & operator=(const QgsConnectionPoolGroup &other)=delete
QgsConnectionPoolGroup cannot be copied.
QgsConnectionPoolGroup::onConnectionExpired
void onConnectionExpired()
Definition: qgsconnectionpool.h:213
QgsConnectionPool::acquireConnection
T acquireConnection(const QString &connInfo, int timeout=-1, bool requestMayBeNested=false, QgsFeedback *feedback=nullptr)
Try to acquire a connection for a maximum of timeout milliseconds.
Definition: qgsconnectionpool.h:298
QgsFeedback
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
QgsConnectionPoolGroup::Item::lastUsedTime
QTime lastUsedTime
Definition: qgsconnectionpool.h:70
QgsConnectionPoolGroup::connInfo
QString connInfo
Definition: qgsconnectionpool.h:243
QgsConnectionPoolGroup::Item
Definition: qgsconnectionpool.h:67
QgsConnectionPool::mGroups
T_Groups mGroups
Definition: qgsconnectionpool.h:359
CONN_POOL_SPARE_CONNECTIONS
#define CONN_POOL_SPARE_CONNECTIONS
Definition: qgsconnectionpool.h:36
QgsApplication
Extends QApplication to provide access to QGIS specific resources such as theme paths,...
Definition: qgsapplication.h:91
QgsConnectionPool::releaseConnection
void releaseConnection(T conn)
Release an existing connection so it will get back into the pool and can be reused.
Definition: qgsconnectionpool.h:331
QgsConnectionPoolGroup::acquiredConns
QList< T > acquiredConns
Definition: qgsconnectionpool.h:245
QgsConnectionPoolGroup::Item::c
T c
Definition: qgsconnectionpool.h:69
c
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
Definition: porting_processing.dox:1
QgsConnectionPoolGroup::~QgsConnectionPoolGroup
~QgsConnectionPoolGroup()
Definition: qgsconnectionpool.h:79
QgsConnectionPoolGroup::invalidateConnections
void invalidateConnections()
Definition: qgsconnectionpool.h:187
QgsConnectionPoolGroup::acquire
T acquire(int timeout, bool requestMayBeNested)
Try to acquire a connection for a maximum of timeout milliseconds.
Definition: qgsconnectionpool.h:99
QgsConnectionPoolGroup::QgsConnectionPoolGroup
QgsConnectionPoolGroup(const QString &ci)
Definition: qgsconnectionpool.h:73
qgsfeedback.h
CONN_POOL_EXPIRATION_TIME
#define CONN_POOL_EXPIRATION_TIME
Definition: qgsconnectionpool.h:35
QgsConnectionPool::mMutex
QMutex mMutex
Definition: qgsconnectionpool.h:360