QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 
58 template <typename T>
60 {
61  public:
62 
63  struct Item
64  {
65  T c;
66  QTime lastUsedTime;
67  };
68 
69  QgsConnectionPoolGroup( const QString &ci )
70  : connInfo( ci )
71  , sem( QgsApplication::instance()->maxConcurrentConnectionsPerPool() + CONN_POOL_SPARE_CONNECTIONS )
72  {
73  }
74 
76  {
77  for ( const Item &item : qgis::as_const( conns ) )
78  {
79  qgsConnectionPool_ConnectionDestroy( item.c );
80  }
81  }
82 
84  QgsConnectionPoolGroup( const QgsConnectionPoolGroup &other ) = delete;
87 
95  T acquire( int timeout, bool requestMayBeNested )
96  {
97  const int requiredFreeConnectionCount = requestMayBeNested ? 1 : 3;
98  // we are going to acquire a resource - if no resource is available, we will block here
99  if ( timeout >= 0 )
100  {
101  if ( !sem.tryAcquire( requiredFreeConnectionCount, timeout ) )
102  return nullptr;
103  }
104  else
105  {
106  // we should still be able to use tryAcquire with a negative timeout here, but
107  // tryAcquire is broken on Qt > 5.8 with negative timeouts - see
108  // https://bugreports.qt.io/browse/QTBUG-64413
109  // https://lists.osgeo.org/pipermail/qgis-developer/2017-November/050456.html
110  sem.acquire( requiredFreeConnectionCount );
111  }
112  sem.release( requiredFreeConnectionCount - 1 );
113 
114  // quick (preferred) way - use cached connection
115  {
116  QMutexLocker locker( &connMutex );
117 
118  if ( !conns.isEmpty() )
119  {
120  Item i = conns.pop();
121  if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
122  {
123  qgsConnectionPool_ConnectionDestroy( i.c );
124  qgsConnectionPool_ConnectionCreate( connInfo, i.c );
125  }
126 
127 
128  // no need to run if nothing can expire
129  if ( conns.isEmpty() )
130  {
131  // will call the slot directly or queue the call (if the object lives in a different thread)
132  QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
133  }
134 
135  acquiredConns.append( i.c );
136 
137  return i.c;
138  }
139  }
140 
141  T c;
142  qgsConnectionPool_ConnectionCreate( connInfo, c );
143  if ( !c )
144  {
145  // we didn't get connection for some reason, so release the lock
146  sem.release();
147  return nullptr;
148  }
149 
150  connMutex.lock();
151  acquiredConns.append( c );
152  connMutex.unlock();
153  return c;
154  }
155 
156  void release( T conn )
157  {
158  connMutex.lock();
159  acquiredConns.removeAll( conn );
160  if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
161  {
162  qgsConnectionPool_ConnectionDestroy( conn );
163  }
164  else
165  {
166  Item i;
167  i.c = conn;
168  i.lastUsedTime = QTime::currentTime();
169  conns.push( i );
170 
171  if ( !expirationTimer->isActive() )
172  {
173  // will call the slot directly or queue the call (if the object lives in a different thread)
174  QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
175  }
176  }
177 
178  connMutex.unlock();
179 
180  sem.release(); // this can unlock a thread waiting in acquire()
181  }
182 
184  {
185  connMutex.lock();
186  for ( const Item &i : qgis::as_const( conns ) )
187  {
188  qgsConnectionPool_ConnectionDestroy( i.c );
189  }
190  conns.clear();
191  for ( T c : qgis::as_const( acquiredConns ) )
192  qgsConnectionPool_InvalidateConnection( c );
193  connMutex.unlock();
194  }
195 
196  protected:
197 
198  void initTimer( QObject *parent )
199  {
200  expirationTimer = new QTimer( parent );
201  expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
202  QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
203 
204  // just to make sure the object belongs to main thread and thus will get events
205  if ( qApp )
206  parent->moveToThread( qApp->thread() );
207  }
208 
210  {
211  connMutex.lock();
212 
213  QTime now = QTime::currentTime();
214 
215  // what connections have expired?
216  QList<int> toDelete;
217  for ( int i = 0; i < conns.count(); ++i )
218  {
219  if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
220  toDelete.append( i );
221  }
222 
223  // delete expired connections
224  for ( int j = toDelete.count() - 1; j >= 0; --j )
225  {
226  int index = toDelete[j];
227  qgsConnectionPool_ConnectionDestroy( conns[index].c );
228  conns.remove( index );
229  }
230 
231  if ( conns.isEmpty() )
232  expirationTimer->stop();
233 
234  connMutex.unlock();
235  }
236 
237  protected:
238 
239  QString connInfo;
240  QStack<Item> conns;
241  QList<T> acquiredConns;
242  QMutex connMutex;
243  QSemaphore sem;
244  QTimer *expirationTimer = nullptr;
245 
246 };
247 
248 
265 template <typename T, typename T_Group>
267 {
268  public:
269 
270  typedef QMap<QString, T_Group *> T_Groups;
271 
273  {
274  mMutex.lock();
275  for ( T_Group *group : qgis::as_const( mGroups ) )
276  {
277  delete group;
278  }
279  mGroups.clear();
280  mMutex.unlock();
281  }
282 
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)