QGIS API Documentation  2.99.0-Master (9fdd060)
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 
88  T acquire()
89  {
90  // we are going to acquire a resource - if no resource is available, we will block here
91  sem.acquire();
92 
93  // quick (preferred) way - use cached connection
94  {
95  QMutexLocker locker( &connMutex );
96 
97  if ( !conns.isEmpty() )
98  {
99  Item i = conns.pop();
100  if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
101  {
102  qgsConnectionPool_ConnectionDestroy( i.c );
103  qgsConnectionPool_ConnectionCreate( connInfo, i.c );
104  }
105 
106  // no need to run if nothing can expire
107  if ( conns.isEmpty() )
108  {
109  // will call the slot directly or queue the call (if the object lives in a different thread)
110  QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
111  }
112 
113  acquiredConns.append( i.c );
114 
115  return i.c;
116  }
117  }
118 
119  T c;
120  qgsConnectionPool_ConnectionCreate( connInfo, c );
121  if ( !c )
122  {
123  // we didn't get connection for some reason, so release the lock
124  sem.release();
125  return nullptr;
126  }
127 
128  connMutex.lock();
129  acquiredConns.append( c );
130  connMutex.unlock();
131  return c;
132  }
133 
134  void release( T conn )
135  {
136  connMutex.lock();
137  acquiredConns.removeAll( conn );
138  if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
139  {
140  qgsConnectionPool_ConnectionDestroy( conn );
141  }
142  else
143  {
144  Item i;
145  i.c = conn;
146  i.lastUsedTime = QTime::currentTime();
147  conns.push( i );
148 
149  if ( !expirationTimer->isActive() )
150  {
151  // will call the slot directly or queue the call (if the object lives in a different thread)
152  QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
153  }
154  }
155 
156  connMutex.unlock();
157 
158  sem.release(); // this can unlock a thread waiting in acquire()
159  }
160 
162  {
163  connMutex.lock();
164  Q_FOREACH ( Item i, conns )
165  {
166  qgsConnectionPool_ConnectionDestroy( i.c );
167  }
168  conns.clear();
169  Q_FOREACH ( T c, acquiredConns )
170  qgsConnectionPool_InvalidateConnection( c );
171  connMutex.unlock();
172  }
173 
174  protected:
175 
176  void initTimer( QObject *parent )
177  {
178  expirationTimer = new QTimer( parent );
179  expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
180  QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
181 
182  // just to make sure the object belongs to main thread and thus will get events
183  if ( qApp )
184  parent->moveToThread( qApp->thread() );
185  }
186 
188  {
189  connMutex.lock();
190 
191  QTime now = QTime::currentTime();
192 
193  // what connections have expired?
194  QList<int> toDelete;
195  for ( int i = 0; i < conns.count(); ++i )
196  {
197  if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
198  toDelete.append( i );
199  }
200 
201  // delete expired connections
202  for ( int j = toDelete.count() - 1; j >= 0; --j )
203  {
204  int index = toDelete[j];
205  qgsConnectionPool_ConnectionDestroy( conns[index].c );
206  conns.remove( index );
207  }
208 
209  if ( conns.isEmpty() )
210  expirationTimer->stop();
211 
212  connMutex.unlock();
213  }
214 
215  protected:
216 
217  QString connInfo;
218  QStack<Item> conns;
219  QList<T> acquiredConns;
220  QMutex connMutex;
221  QSemaphore sem;
222  QTimer *expirationTimer = nullptr;
223 
224 };
225 
226 
243 template <typename T, typename T_Group>
245 {
246  public:
247 
248  typedef QMap<QString, T_Group *> T_Groups;
249 
251  {
252  mMutex.lock();
253  Q_FOREACH ( T_Group *group, mGroups )
254  {
255  delete group;
256  }
257  mGroups.clear();
258  mMutex.unlock();
259  }
260 
265  T acquireConnection( const QString &connInfo )
266  {
267  mMutex.lock();
268  typename T_Groups::iterator it = mGroups.find( connInfo );
269  if ( it == mGroups.end() )
270  {
271  it = mGroups.insert( connInfo, new T_Group( connInfo ) );
272  }
273  T_Group *group = *it;
274  mMutex.unlock();
275 
276  return group->acquire();
277  }
278 
280  void releaseConnection( T conn )
281  {
282  mMutex.lock();
283  typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
284  Q_ASSERT( it != mGroups.end() );
285  T_Group *group = *it;
286  mMutex.unlock();
287 
288  group->release( conn );
289  }
290 
298  void invalidateConnections( const QString &connInfo )
299  {
300  mMutex.lock();
301  if ( mGroups.contains( connInfo ) )
302  mGroups[connInfo]->invalidateConnections();
303  mMutex.unlock();
304  }
305 
306 
307  protected:
308  T_Groups mGroups;
309  QMutex mMutex;
310 };
311 
312 
313 #endif // QGSCONNECTIONPOOL_H
#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 acquireConnection(const QString &connInfo)
Try to acquire a connection: if no connections are available, the thread will get blocked...
Template class responsible for keeping a pool of open connections.
Template that stores data related to one server.
QgsConnectionPoolGroup(const QString &ci)