QGIS API Documentation  2.99.0-Master (9ed189e)
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 #include <QCoreApplication>
20 #include <QMap>
21 #include <QMutex>
22 #include <QSemaphore>
23 #include <QStack>
24 #include <QTime>
25 #include <QTimer>
26 #include <QThread>
27 
28 
29 #define CONN_POOL_MAX_CONCURRENT_CONNS 4
30 #define CONN_POOL_EXPIRATION_TIME 60 // in seconds
31 
32 
53 template <typename T>
55 {
56  public:
57 
58  static const int MAX_CONCURRENT_CONNECTIONS;
59 
60  struct Item
61  {
62  T c;
63  QTime lastUsedTime;
64  };
65 
66  QgsConnectionPoolGroup( const QString& ci )
67  : connInfo( ci )
69  , expirationTimer( nullptr )
70  {
71  }
72 
74  {
75  Q_FOREACH ( Item item, conns )
76  {
77  qgsConnectionPool_ConnectionDestroy( item.c );
78  }
79  }
80 
82  QgsConnectionPoolGroup( const QgsConnectionPoolGroup& other ) = delete;
85 
86  T acquire()
87  {
88  // we are going to acquire a resource - if no resource is available, we will block here
89  sem.acquire();
90 
91  // quick (preferred) way - use cached connection
92  {
93  QMutexLocker locker( &connMutex );
94 
95  if ( !conns.isEmpty() )
96  {
97  Item i = conns.pop();
98  if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
99  {
100  qgsConnectionPool_ConnectionDestroy( i.c );
101  qgsConnectionPool_ConnectionCreate( connInfo, i.c );
102  }
103 
104  // no need to run if nothing can expire
105  if ( conns.isEmpty() )
106  {
107  // will call the slot directly or queue the call (if the object lives in a different thread)
108  QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
109  }
110 
111  acquiredConns.append( i.c );
112 
113  return i.c;
114  }
115  }
116 
117  T c;
118  qgsConnectionPool_ConnectionCreate( connInfo, c );
119  if ( !c )
120  {
121  // we didn't get connection for some reason, so release the lock
122  sem.release();
123  return nullptr;
124  }
125 
126  connMutex.lock();
127  acquiredConns.append( c );
128  connMutex.unlock();
129  return c;
130  }
131 
132  void release( T conn )
133  {
134  connMutex.lock();
135  acquiredConns.removeAll( conn );
136  if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
137  {
138  qgsConnectionPool_ConnectionDestroy( conn );
139  }
140  else
141  {
142  Item i;
143  i.c = conn;
144  i.lastUsedTime = QTime::currentTime();
145  conns.push( i );
146 
147  if ( !expirationTimer->isActive() )
148  {
149  // will call the slot directly or queue the call (if the object lives in a different thread)
150  QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
151  }
152  }
153 
154  connMutex.unlock();
155 
156  sem.release(); // this can unlock a thread waiting in acquire()
157  }
158 
160  {
161  connMutex.lock();
162  Q_FOREACH ( Item i, conns )
163  {
164  qgsConnectionPool_ConnectionDestroy( i.c );
165  }
166  conns.clear();
167  Q_FOREACH ( T c, acquiredConns )
168  qgsConnectionPool_InvalidateConnection( c );
169  connMutex.unlock();
170  }
171 
172  protected:
173 
174  void initTimer( QObject* parent )
175  {
176  expirationTimer = new QTimer( parent );
177  expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
178  QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
179 
180  // just to make sure the object belongs to main thread and thus will get events
181  if ( qApp )
182  parent->moveToThread( qApp->thread() );
183  }
184 
186  {
187  connMutex.lock();
188 
189  QTime now = QTime::currentTime();
190 
191  // what connections have expired?
192  QList<int> toDelete;
193  for ( int i = 0; i < conns.count(); ++i )
194  {
195  if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
196  toDelete.append( i );
197  }
198 
199  // delete expired connections
200  for ( int j = toDelete.count() - 1; j >= 0; --j )
201  {
202  int index = toDelete[j];
203  qgsConnectionPool_ConnectionDestroy( conns[index].c );
204  conns.remove( index );
205  }
206 
207  if ( conns.isEmpty() )
208  expirationTimer->stop();
209 
210  connMutex.unlock();
211  }
212 
213  protected:
214 
215  QString connInfo;
216  QStack<Item> conns;
217  QList<T> acquiredConns;
218  QMutex connMutex;
219  QSemaphore sem;
220  QTimer* expirationTimer = nullptr;
221 
222 };
223 
224 
240 template <typename T, typename T_Group>
242 {
243  public:
244 
245  typedef QMap<QString, T_Group*> T_Groups;
246 
248  {
249  mMutex.lock();
250  Q_FOREACH ( T_Group* group, mGroups )
251  {
252  delete group;
253  }
254  mGroups.clear();
255  mMutex.unlock();
256  }
257 
260  T acquireConnection( const QString& connInfo )
261  {
262  mMutex.lock();
263  typename T_Groups::iterator it = mGroups.find( connInfo );
264  if ( it == mGroups.end() )
265  {
266  it = mGroups.insert( connInfo, new T_Group( connInfo ) );
267  }
268  T_Group* group = *it;
269  mMutex.unlock();
270 
271  return group->acquire();
272  }
273 
275  void releaseConnection( T conn )
276  {
277  mMutex.lock();
278  typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
279  Q_ASSERT( it != mGroups.end() );
280  T_Group* group = *it;
281  mMutex.unlock();
282 
283  group->release( conn );
284  }
285 
291  void invalidateConnections( const QString& connInfo )
292  {
293  mMutex.lock();
294  if ( mGroups.contains( connInfo ) )
295  mGroups[connInfo]->invalidateConnections();
296  mMutex.unlock();
297  }
298 
299 
300  protected:
301  T_Groups mGroups;
302  QMutex mMutex;
303 };
304 
305 
306 #endif // QGSCONNECTIONPOOL_H
static unsigned index
#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)