QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 
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
Extends QApplication to provide access to QGIS specific resources such as theme paths,...
Template that stores data related to a connection to a single server or datasource.
QgsConnectionPoolGroup(const QgsConnectionPoolGroup &other)=delete
QgsConnectionPoolGroup cannot be copied.
T acquire(int timeout, bool requestMayBeNested)
Try to acquire a connection for a maximum of timeout milliseconds.
QgsConnectionPoolGroup(const QString &ci)
QgsConnectionPoolGroup & operator=(const QgsConnectionPoolGroup &other)=delete
QgsConnectionPoolGroup cannot be copied.
void initTimer(QObject *parent)
Template class responsible for keeping a pool of open connections.
QMap< QString, T_Group * > T_Groups
void invalidateConnections(const QString &connInfo)
Invalidates all connections to the specified resource.
virtual ~QgsConnectionPool()
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.
void releaseConnection(T conn)
Release an existing connection so it will get back into the pool and can be reused.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
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
#define CONN_POOL_SPARE_CONNECTIONS
#define CONN_POOL_EXPIRATION_TIME