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