QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 <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 
60 template <typename T>
62 {
63  public:
64 
65  struct Item
66  {
67  T c;
68  QTime lastUsedTime;
69  };
70 
71  QgsConnectionPoolGroup( const QString &ci )
72  : connInfo( ci )
73  , sem( QgsApplication::instance()->maxConcurrentConnectionsPerPool() + CONN_POOL_SPARE_CONNECTIONS )
74  {
75  }
76 
78  {
79  for ( const Item &item : std::as_const( conns ) )
80  {
81  qgsConnectionPool_ConnectionDestroy( item.c );
82  }
83  }
84 
89 
97  T acquire( int timeout, bool requestMayBeNested )
98  {
99  const int requiredFreeConnectionCount = requestMayBeNested ? 1 : 3;
100  // we are going to acquire a resource - if no resource is available, we will block here
101  if ( timeout >= 0 )
102  {
103  if ( !sem.tryAcquire( requiredFreeConnectionCount, timeout ) )
104  return nullptr;
105  }
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  sem.acquire( requiredFreeConnectionCount );
113  }
114  sem.release( requiredFreeConnectionCount - 1 );
115 
116  // quick (preferred) way - use cached connection
117  {
118  QMutexLocker locker( &connMutex );
119 
120  if ( !conns.isEmpty() )
121  {
122  Item i = conns.pop();
123  if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
124  {
125  qgsConnectionPool_ConnectionDestroy( i.c );
126  qgsConnectionPool_ConnectionCreate( connInfo, i.c );
127  }
128 
129 
130  // no need to run if nothing can expire
131  if ( conns.isEmpty() )
132  {
133  // will call the slot directly or queue the call (if the object lives in a different thread)
134  QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
135  }
136 
137  acquiredConns.append( i.c );
138 
139  return i.c;
140  }
141  }
142 
143  T c;
144  qgsConnectionPool_ConnectionCreate( connInfo, c );
145  if ( !c )
146  {
147  // we didn't get connection for some reason, so release the lock
148  sem.release();
149  return nullptr;
150  }
151 
152  connMutex.lock();
153  acquiredConns.append( c );
154  connMutex.unlock();
155  return c;
156  }
157 
158  void release( T conn )
159  {
160  connMutex.lock();
161  acquiredConns.removeAll( conn );
162  if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
163  {
164  qgsConnectionPool_ConnectionDestroy( conn );
165  }
166  else
167  {
168  Item i;
169  i.c = conn;
170  i.lastUsedTime = QTime::currentTime();
171  conns.push( i );
172 
173  if ( !expirationTimer->isActive() )
174  {
175  // will call the slot directly or queue the call (if the object lives in a different thread)
176  QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
177  }
178  }
179 
180  connMutex.unlock();
181 
182  sem.release(); // this can unlock a thread waiting in acquire()
183  }
184 
186  {
187  connMutex.lock();
188  for ( const Item &i : std::as_const( conns ) )
189  {
190  qgsConnectionPool_ConnectionDestroy( i.c );
191  }
192  conns.clear();
193  for ( T c : std::as_const( acquiredConns ) )
194  qgsConnectionPool_InvalidateConnection( c );
195  connMutex.unlock();
196  }
197 
198  protected:
199 
200  void initTimer( QObject *parent )
201  {
202  expirationTimer = new QTimer( parent );
203  expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
204  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  if ( qApp )
208  parent->moveToThread( qApp->thread() );
209  }
210 
212  {
213  connMutex.lock();
214 
215  QTime now = QTime::currentTime();
216 
217  // what connections have expired?
218  QList<int> toDelete;
219  for ( int i = 0; i < conns.count(); ++i )
220  {
221  if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
222  toDelete.append( i );
223  }
224 
225  // delete expired connections
226  for ( int j = toDelete.count() - 1; j >= 0; --j )
227  {
228  int index = toDelete[j];
229  qgsConnectionPool_ConnectionDestroy( conns[index].c );
230  conns.remove( index );
231  }
232 
233  if ( conns.isEmpty() )
234  expirationTimer->stop();
235 
236  connMutex.unlock();
237  }
238 
239  protected:
240 
241  QString connInfo;
242  QStack<Item> conns;
243  QList<T> acquiredConns;
244  QMutex connMutex;
245  QSemaphore sem;
246  QTimer *expirationTimer = nullptr;
247 
248 };
249 
250 
268 template <typename T, typename T_Group>
270 {
271  public:
272 
273  typedef QMap<QString, T_Group *> T_Groups;
274 
276  {
277  mMutex.lock();
278  for ( T_Group *group : std::as_const( mGroups ) )
279  {
280  delete group;
281  }
282  mGroups.clear();
283  mMutex.unlock();
284  }
285 
293  T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false )
294  {
295  mMutex.lock();
296  typename T_Groups::iterator it = mGroups.find( connInfo );
297  if ( it == mGroups.end() )
298  {
299  it = mGroups.insert( connInfo, new T_Group( connInfo ) );
300  }
301  T_Group *group = *it;
302  mMutex.unlock();
303 
304  return group->acquire( timeout, requestMayBeNested );
305  }
306 
308  void releaseConnection( T conn )
309  {
310  mMutex.lock();
311  typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
312  Q_ASSERT( it != mGroups.end() );
313  T_Group *group = *it;
314  mMutex.unlock();
315 
316  group->release( conn );
317  }
318 
326  void invalidateConnections( const QString &connInfo )
327  {
328  mMutex.lock();
329  if ( mGroups.contains( connInfo ) )
330  mGroups[connInfo]->invalidateConnections();
331  mMutex.unlock();
332  }
333 
334 
335  protected:
337  QMutex mMutex;
338 };
339 
340 
341 #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.
T acquireConnection(const QString &connInfo, int timeout=-1, bool requestMayBeNested=false)
Try to acquire a connection for a maximum of timeout milliseconds.
QMap< QString, T_Group * > T_Groups
void invalidateConnections(const QString &connInfo)
Invalidates all connections to the specified resource.
virtual ~QgsConnectionPool()
void releaseConnection(T conn)
Release an existing connection so it will get back into the pool and can be reused.
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