QGIS API Documentation  2.99.0-Master (5b186ae)
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 
55 template <typename T>
57 {
58  public:
59 
60  static const int MAX_CONCURRENT_CONNECTIONS;
61 
62  struct Item
63  {
64  T c;
65  QTime lastUsedTime;
66  };
67 
68  QgsConnectionPoolGroup( const QString &ci )
69  : connInfo( ci )
71  , expirationTimer( nullptr )
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 
242 template <typename T, typename T_Group>
244 {
245  public:
246 
247  typedef QMap<QString, T_Group *> T_Groups;
248 
250  {
251  mMutex.lock();
252  Q_FOREACH ( T_Group *group, mGroups )
253  {
254  delete group;
255  }
256  mGroups.clear();
257  mMutex.unlock();
258  }
259 
262  T acquireConnection( const QString &connInfo )
263  {
264  mMutex.lock();
265  typename T_Groups::iterator it = mGroups.find( connInfo );
266  if ( it == mGroups.end() )
267  {
268  it = mGroups.insert( connInfo, new T_Group( connInfo ) );
269  }
270  T_Group *group = *it;
271  mMutex.unlock();
272 
273  return group->acquire();
274  }
275 
277  void releaseConnection( T conn )
278  {
279  mMutex.lock();
280  typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
281  Q_ASSERT( it != mGroups.end() );
282  T_Group *group = *it;
283  mMutex.unlock();
284 
285  group->release( conn );
286  }
287 
293  void invalidateConnections( const QString &connInfo )
294  {
295  mMutex.lock();
296  if ( mGroups.contains( connInfo ) )
297  mGroups[connInfo]->invalidateConnections();
298  mMutex.unlock();
299  }
300 
301 
302  protected:
303  T_Groups mGroups;
304  QMutex mMutex;
305 };
306 
307 
308 #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)