QGIS API Documentation 3.27.0-Master (95e00c50d2)
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 "qgsapplication.h"
23#include "qgsfeedback.h"
24
25#include <QCoreApplication>
26#include <QMap>
27#include <QMutex>
28#include <QSemaphore>
29#include <QStack>
30#include <QTime>
31#include <QTimer>
32#include <QThread>
33#include <QElapsedTimer>
34
35#define CONN_POOL_EXPIRATION_TIME 60 // in seconds
36#define CONN_POOL_SPARE_CONNECTIONS 2 // number of spare connections in case all the base connections are used but we have a nested request with the risk of a deadlock
37
38
62template <typename T>
64{
65 public:
66
67 struct Item
68 {
69 T c;
71 };
72
73 QgsConnectionPoolGroup( const QString &ci )
74 : connInfo( ci )
75 , sem( QgsApplication::instance()->maxConcurrentConnectionsPerPool() + CONN_POOL_SPARE_CONNECTIONS )
76 {
77 }
78
80 {
81 for ( const Item &item : std::as_const( conns ) )
82 {
83 qgsConnectionPool_ConnectionDestroy( item.c );
84 }
85 }
86
91
99 T acquire( int timeout, bool requestMayBeNested )
100 {
101 const int requiredFreeConnectionCount = requestMayBeNested ? 1 : 3;
102 // we are going to acquire a resource - if no resource is available, we will block here
103 if ( timeout >= 0 )
104 {
105 if ( !sem.tryAcquire( requiredFreeConnectionCount, timeout ) )
106 return nullptr;
107 }
108 else
109 {
110 // we should still be able to use tryAcquire with a negative timeout here, but
111 // tryAcquire is broken on Qt > 5.8 with negative timeouts - see
112 // https://bugreports.qt.io/browse/QTBUG-64413
113 // https://lists.osgeo.org/pipermail/qgis-developer/2017-November/050456.html
114 sem.acquire( requiredFreeConnectionCount );
115 }
116 sem.release( requiredFreeConnectionCount - 1 );
117
118 // quick (preferred) way - use cached connection
119 {
120 QMutexLocker locker( &connMutex );
121
122 if ( !conns.isEmpty() )
123 {
124 Item i = conns.pop();
125 if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
126 {
127 qgsConnectionPool_ConnectionDestroy( i.c );
128 qgsConnectionPool_ConnectionCreate( connInfo, i.c );
129 }
130
131
132 // no need to run if nothing can expire
133 if ( conns.isEmpty() )
134 {
135 // will call the slot directly or queue the call (if the object lives in a different thread)
136 QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
137 }
138
139 acquiredConns.append( i.c );
140
141 return i.c;
142 }
143 }
144
145 T c;
146 qgsConnectionPool_ConnectionCreate( connInfo, c );
147 if ( !c )
148 {
149 // we didn't get connection for some reason, so release the lock
150 sem.release();
151 return nullptr;
152 }
153
154 connMutex.lock();
155 acquiredConns.append( c );
156 connMutex.unlock();
157 return c;
158 }
159
160 void release( T conn )
161 {
162 connMutex.lock();
163 acquiredConns.removeAll( conn );
164 if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
165 {
166 qgsConnectionPool_ConnectionDestroy( conn );
167 }
168 else
169 {
170 Item i;
171 i.c = conn;
172 i.lastUsedTime = QTime::currentTime();
173 conns.push( i );
174
175 if ( !expirationTimer->isActive() )
176 {
177 // will call the slot directly or queue the call (if the object lives in a different thread)
178 QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
179 }
180 }
181
182 connMutex.unlock();
183
184 sem.release(); // this can unlock a thread waiting in acquire()
185 }
186
188 {
189 connMutex.lock();
190 for ( const Item &i : std::as_const( conns ) )
191 {
192 qgsConnectionPool_ConnectionDestroy( i.c );
193 }
194 conns.clear();
195 for ( T c : std::as_const( acquiredConns ) )
196 qgsConnectionPool_InvalidateConnection( c );
197 connMutex.unlock();
198 }
199
200 protected:
201
202 void initTimer( QObject *parent )
203 {
204 expirationTimer = new QTimer( parent );
205 expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
206 QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
207
208 // just to make sure the object belongs to main thread and thus will get events
209 if ( qApp )
210 parent->moveToThread( qApp->thread() );
211 }
212
214 {
215 connMutex.lock();
216
217 QTime now = QTime::currentTime();
218
219 // what connections have expired?
220 QList<int> toDelete;
221 for ( int i = 0; i < conns.count(); ++i )
222 {
223 if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
224 toDelete.append( i );
225 }
226
227 // delete expired connections
228 for ( int j = toDelete.count() - 1; j >= 0; --j )
229 {
230 int index = toDelete[j];
231 qgsConnectionPool_ConnectionDestroy( conns[index].c );
232 conns.remove( index );
233 }
234
235 if ( conns.isEmpty() )
236 expirationTimer->stop();
237
238 connMutex.unlock();
239 }
240
241 protected:
242
243 QString connInfo;
244 QStack<Item> conns;
246 QMutex connMutex;
247 QSemaphore sem;
248 QTimer *expirationTimer = nullptr;
249
250};
251
252
270template <typename T, typename T_Group>
272{
273 public:
274
275 typedef QMap<QString, T_Group *> T_Groups;
276
278 {
279 mMutex.lock();
280 for ( T_Group *group : std::as_const( mGroups ) )
281 {
282 delete group;
283 }
284 mGroups.clear();
285 mMutex.unlock();
286 }
287
298 T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false, QgsFeedback *feedback = nullptr )
299 {
300 mMutex.lock();
301 typename T_Groups::iterator it = mGroups.find( connInfo );
302 if ( it == mGroups.end() )
303 {
304 it = mGroups.insert( connInfo, new T_Group( connInfo ) );
305 }
306 T_Group *group = *it;
307 mMutex.unlock();
308
309 if ( feedback )
310 {
311 QElapsedTimer timer;
312 timer.start();
313
314 while ( !feedback->isCanceled() )
315 {
316 if ( T conn = group->acquire( 300, requestMayBeNested ) )
317 return conn;
318
319 if ( timeout > 0 && timer.elapsed() >= timeout )
320 return nullptr;
321 }
322 return nullptr;
323 }
324 else
325 {
326 return group->acquire( timeout, requestMayBeNested );
327 }
328 }
329
331 void releaseConnection( T conn )
332 {
333 mMutex.lock();
334 typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
335 Q_ASSERT( it != mGroups.end() );
336 T_Group *group = *it;
337 mMutex.unlock();
338
339 group->release( conn );
340 }
341
349 void invalidateConnections( const QString &connInfo )
350 {
351 mMutex.lock();
352 if ( mGroups.contains( connInfo ) )
353 mGroups[connInfo]->invalidateConnections();
354 mMutex.unlock();
355 }
356
357
358 protected:
360 QMutex mMutex;
361};
362
363
364#endif // QGSCONNECTIONPOOL_H
Extends QApplication to provide access to QGIS specific resources such as theme paths,...
Template that stores data related to a connection to a single server or datasource.
QgsConnectionPoolGroup(const QgsConnectionPoolGroup &other)=delete
QgsConnectionPoolGroup cannot be copied.
T acquire(int timeout, bool requestMayBeNested)
Try to acquire a connection for a maximum of timeout milliseconds.
QgsConnectionPoolGroup(const QString &ci)
QgsConnectionPoolGroup & operator=(const QgsConnectionPoolGroup &other)=delete
QgsConnectionPoolGroup cannot be copied.
void initTimer(QObject *parent)
Template class responsible for keeping a pool of open connections.
QMap< QString, T_Group * > T_Groups
void invalidateConnections(const QString &connInfo)
Invalidates all connections to the specified resource.
virtual ~QgsConnectionPool()
T acquireConnection(const QString &connInfo, int timeout=-1, bool requestMayBeNested=false, QgsFeedback *feedback=nullptr)
Try to acquire a connection for a maximum of timeout milliseconds.
void releaseConnection(T conn)
Release an existing connection so it will get back into the pool and can be reused.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define CONN_POOL_SPARE_CONNECTIONS
#define CONN_POOL_EXPIRATION_TIME