QGIS API Documentation  3.17.0-Master (8af46bc54f)
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 : qgis::as_const( conns ) )
80  {
81  qgsConnectionPool_ConnectionDestroy( item.c );
82  }
83  }
84 
86  QgsConnectionPoolGroup( const QgsConnectionPoolGroup &other ) = delete;
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 : qgis::as_const( conns ) )
189  {
190  qgsConnectionPool_ConnectionDestroy( i.c );
191  }
192  conns.clear();
193  for ( T c : qgis::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 
267 template <typename T, typename T_Group>
269 {
270  public:
271 
272  typedef QMap<QString, T_Group *> T_Groups;
273 
275  {
276  mMutex.lock();
277  for ( T_Group *group : qgis::as_const( mGroups ) )
278  {
279  delete group;
280  }
281  mGroups.clear();
282  mMutex.unlock();
283  }
284 
292  T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false )
293  {
294  mMutex.lock();
295  typename T_Groups::iterator it = mGroups.find( connInfo );
296  if ( it == mGroups.end() )
297  {
298  it = mGroups.insert( connInfo, new T_Group( connInfo ) );
299  }
300  T_Group *group = *it;
301  mMutex.unlock();
302 
303  return group->acquire( timeout, requestMayBeNested );
304  }
305 
307  void releaseConnection( T conn )
308  {
309  mMutex.lock();
310  typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
311  Q_ASSERT( it != mGroups.end() );
312  T_Group *group = *it;
313  mMutex.unlock();
314 
315  group->release( conn );
316  }
317 
325  void invalidateConnections( const QString &connInfo )
326  {
327  mMutex.lock();
328  if ( mGroups.contains( connInfo ) )
329  mGroups[connInfo]->invalidateConnections();
330  mMutex.unlock();
331  }
332 
333 
334  protected:
335  T_Groups mGroups;
336  QMutex mMutex;
337 };
338 
339 
340 #endif // QGSCONNECTIONPOOL_H
Extends QApplication to provide access to QGIS specific resources such as theme paths, database paths etc.
virtual ~QgsConnectionPool()
#define CONN_POOL_EXPIRATION_TIME
void invalidateConnections(const QString &connInfo)
Invalidates all connections to the specified resource.
QMap< QString, T_Group * > T_Groups
T acquireConnection(const QString &connInfo, int timeout=-1, bool requestMayBeNested=false)
Try to acquire a connection for a maximum of timeout milliseconds.
void initTimer(QObject *parent)
QgsConnectionPoolGroup & operator=(const QgsConnectionPoolGroup &other)=delete
QgsConnectionPoolGroup cannot be copied.
void releaseConnection(T conn)
Release an existing connection so it will get back into the pool and can be reused.
T acquire(int timeout, bool requestMayBeNested)
Try to acquire a connection for a maximum of timeout milliseconds.
#define CONN_POOL_SPARE_CONNECTIONS
Template class responsible for keeping a pool of open connections.
Template that stores data related to a connection to a single server or datasource.
QgsConnectionPoolGroup(const QString &ci)