QGIS API Documentation  3.2.0-Bonn (bc43194)
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 <QCoreApplication>
23 #include <QMap>
24 #include <QMutex>
25 #include <QSemaphore>
26 #include <QStack>
27 #include <QTime>
28 #include <QTimer>
29 #include <QThread>
30 
31 
32 #define CONN_POOL_MAX_CONCURRENT_CONNS 4
33 #define CONN_POOL_EXPIRATION_TIME 60 // in seconds
34 
35 
57 template <typename T>
59 {
60  public:
61 
62  static const int MAX_CONCURRENT_CONNECTIONS;
63 
64  struct Item
65  {
66  T c;
67  QTime lastUsedTime;
68  };
69 
70  QgsConnectionPoolGroup( const QString &ci )
71  : connInfo( ci )
73  {
74  }
75 
77  {
78  for ( const Item &item : qgis::as_const( conns ) )
79  {
80  qgsConnectionPool_ConnectionDestroy( item.c );
81  }
82  }
83 
85  QgsConnectionPoolGroup( const QgsConnectionPoolGroup &other ) = delete;
88 
96  T acquire( int timeout )
97  {
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( 1, 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( 1 );
111  }
112 
113  // quick (preferred) way - use cached connection
114  {
115  QMutexLocker locker( &connMutex );
116 
117  if ( !conns.isEmpty() )
118  {
119  Item i = conns.pop();
120  if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
121  {
122  qgsConnectionPool_ConnectionDestroy( i.c );
123  qgsConnectionPool_ConnectionCreate( connInfo, i.c );
124  }
125 
126  // no need to run if nothing can expire
127  if ( conns.isEmpty() )
128  {
129  // will call the slot directly or queue the call (if the object lives in a different thread)
130  QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
131  }
132 
133  acquiredConns.append( i.c );
134 
135  return i.c;
136  }
137  }
138 
139  T c;
140  qgsConnectionPool_ConnectionCreate( connInfo, c );
141  if ( !c )
142  {
143  // we didn't get connection for some reason, so release the lock
144  sem.release();
145  return nullptr;
146  }
147 
148  connMutex.lock();
149  acquiredConns.append( c );
150  connMutex.unlock();
151  return c;
152  }
153 
154  void release( T conn )
155  {
156  connMutex.lock();
157  acquiredConns.removeAll( conn );
158  if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
159  {
160  qgsConnectionPool_ConnectionDestroy( conn );
161  }
162  else
163  {
164  Item i;
165  i.c = conn;
166  i.lastUsedTime = QTime::currentTime();
167  conns.push( i );
168 
169  if ( !expirationTimer->isActive() )
170  {
171  // will call the slot directly or queue the call (if the object lives in a different thread)
172  QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
173  }
174  }
175 
176  connMutex.unlock();
177 
178  sem.release(); // this can unlock a thread waiting in acquire()
179  }
180 
182  {
183  connMutex.lock();
184  for ( const Item &i : qgis::as_const( conns ) )
185  {
186  qgsConnectionPool_ConnectionDestroy( i.c );
187  }
188  conns.clear();
189  for ( T c : qgis::as_const( acquiredConns ) )
190  qgsConnectionPool_InvalidateConnection( c );
191  connMutex.unlock();
192  }
193 
194  protected:
195 
196  void initTimer( QObject *parent )
197  {
198  expirationTimer = new QTimer( parent );
199  expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
200  QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
201 
202  // just to make sure the object belongs to main thread and thus will get events
203  if ( qApp )
204  parent->moveToThread( qApp->thread() );
205  }
206 
208  {
209  connMutex.lock();
210 
211  QTime now = QTime::currentTime();
212 
213  // what connections have expired?
214  QList<int> toDelete;
215  for ( int i = 0; i < conns.count(); ++i )
216  {
217  if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
218  toDelete.append( i );
219  }
220 
221  // delete expired connections
222  for ( int j = toDelete.count() - 1; j >= 0; --j )
223  {
224  int index = toDelete[j];
225  qgsConnectionPool_ConnectionDestroy( conns[index].c );
226  conns.remove( index );
227  }
228 
229  if ( conns.isEmpty() )
230  expirationTimer->stop();
231 
232  connMutex.unlock();
233  }
234 
235  protected:
236 
237  QString connInfo;
238  QStack<Item> conns;
239  QList<T> acquiredConns;
240  QMutex connMutex;
241  QSemaphore sem;
242  QTimer *expirationTimer = nullptr;
243 
244 };
245 
246 
263 template <typename T, typename T_Group>
265 {
266  public:
267 
268  typedef QMap<QString, T_Group *> T_Groups;
269 
271  {
272  mMutex.lock();
273  for ( T_Group *group : qgis::as_const( mGroups ) )
274  {
275  delete group;
276  }
277  mGroups.clear();
278  mMutex.unlock();
279  }
280 
288  T acquireConnection( const QString &connInfo, int timeout = -1 )
289  {
290  mMutex.lock();
291  typename T_Groups::iterator it = mGroups.find( connInfo );
292  if ( it == mGroups.end() )
293  {
294  it = mGroups.insert( connInfo, new T_Group( connInfo ) );
295  }
296  T_Group *group = *it;
297  mMutex.unlock();
298 
299  return group->acquire( timeout );
300  }
301 
303  void releaseConnection( T conn )
304  {
305  mMutex.lock();
306  typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
307  Q_ASSERT( it != mGroups.end() );
308  T_Group *group = *it;
309  mMutex.unlock();
310 
311  group->release( conn );
312  }
313 
321  void invalidateConnections( const QString &connInfo )
322  {
323  mMutex.lock();
324  if ( mGroups.contains( connInfo ) )
325  mGroups[connInfo]->invalidateConnections();
326  mMutex.unlock();
327  }
328 
329 
330  protected:
331  T_Groups mGroups;
332  QMutex mMutex;
333 };
334 
335 
336 #endif // QGSCONNECTIONPOOL_H
T acquireConnection(const QString &connInfo, int timeout=-1)
Try to acquire a connection for a maximum of timeout milliseconds.
#define CONN_POOL_MAX_CONCURRENT_CONNS
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
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.
static const int MAX_CONCURRENT_CONNECTIONS
T acquire(int timeout)
Try to acquire a connection for a maximum of timeout milliseconds.
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)