QGIS API Documentation 3.41.0-Master (45a0abf3bec)
Loading...
Searching...
No Matches
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
76 QgsConnectionPoolGroup( const QString &ci )
77 : connInfo( ci )
78 , sem( QgsApplication::instance()->maxConcurrentConnectionsPerPool() + CONN_POOL_SPARE_CONNECTIONS )
79 {
80 }
81
83 {
84 for ( const Item &item : std::as_const( conns ) )
85 {
86 qgsConnectionPool_ConnectionDestroy( item.c );
87 }
88 }
89
92
100 T acquire( int timeout, bool requestMayBeNested )
101 {
102 const int requiredFreeConnectionCount = requestMayBeNested ? 1 : 3;
103 // we are going to acquire a resource - if no resource is available, we will block here
104 if ( timeout >= 0 )
105 {
106 if ( !sem.tryAcquire( requiredFreeConnectionCount, timeout ) )
107 return nullptr;
108 }
109 else
110 {
111 // we should still be able to use tryAcquire with a negative timeout here, but
112 // tryAcquire is broken on Qt > 5.8 with negative timeouts - see
113 // https://bugreports.qt.io/browse/QTBUG-64413
114 // https://lists.osgeo.org/pipermail/qgis-developer/2017-November/050456.html
115 sem.acquire( requiredFreeConnectionCount );
116 }
117 sem.release( requiredFreeConnectionCount - 1 );
118
119 // quick (preferred) way - use cached connection
120 {
121 QMutexLocker locker( &connMutex );
122
123 if ( !conns.isEmpty() )
124 {
125 Item i = conns.pop();
126 if ( !qgsConnectionPool_ConnectionIsValid( i.c ) )
127 {
128 qgsConnectionPool_ConnectionDestroy( i.c );
129 qgsConnectionPool_ConnectionCreate( connInfo, i.c );
130 }
131
132
133 // no need to run if nothing can expire
134 if ( conns.isEmpty() )
135 {
136 // will call the slot directly or queue the call (if the object lives in a different thread)
137 QMetaObject::invokeMethod( expirationTimer->parent(), "stopExpirationTimer" );
138 }
139
140 acquiredConns.append( i.c );
141
142 return i.c;
143 }
144 }
145
146 T c;
147 qgsConnectionPool_ConnectionCreate( connInfo, c );
148 if ( !c )
149 {
150 // we didn't get connection for some reason, so release the lock
151 sem.release();
152 return nullptr;
153 }
154
155 connMutex.lock();
156 acquiredConns.append( c );
157 connMutex.unlock();
158 return c;
159 }
160
161 void release( T conn )
162 {
163 connMutex.lock();
164 acquiredConns.removeAll( conn );
165 if ( !qgsConnectionPool_ConnectionIsValid( conn ) )
166 {
167 qgsConnectionPool_ConnectionDestroy( conn );
168 }
169 else
170 {
171 Item i;
172 i.c = conn;
173 i.lastUsedTime = QTime::currentTime();
174 conns.push( i );
175
176 if ( !expirationTimer->isActive() )
177 {
178 // will call the slot directly or queue the call (if the object lives in a different thread)
179 QMetaObject::invokeMethod( expirationTimer->parent(), "startExpirationTimer" );
180 }
181 }
182
183 connMutex.unlock();
184
185 sem.release(); // this can unlock a thread waiting in acquire()
186 }
187
189 {
190 connMutex.lock();
191 for ( const Item &i : std::as_const( conns ) )
192 {
193 qgsConnectionPool_ConnectionDestroy( i.c );
194 }
195 conns.clear();
196 for ( T c : std::as_const( acquiredConns ) )
197 qgsConnectionPool_InvalidateConnection( c );
198 connMutex.unlock();
199 }
200
201 protected:
202
203 void initTimer( QObject *parent )
204 {
205 expirationTimer = new QTimer( parent );
206 expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
207 QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
208
209 // just to make sure the object belongs to main thread and thus will get events
210 if ( qApp )
211 parent->moveToThread( qApp->thread() );
212 }
213
215 {
216 connMutex.lock();
217
218 QTime now = QTime::currentTime();
219
220 // what connections have expired?
221 QList<int> toDelete;
222 for ( int i = 0; i < conns.count(); ++i )
223 {
224 if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
225 toDelete.append( i );
226 }
227
228 // delete expired connections
229 for ( int j = toDelete.count() - 1; j >= 0; --j )
230 {
231 int index = toDelete[j];
232 qgsConnectionPool_ConnectionDestroy( conns[index].c );
233 conns.remove( index );
234 }
235
236 if ( conns.isEmpty() )
237 expirationTimer->stop();
238
239 connMutex.unlock();
240 }
241
242 protected:
243
244 QString connInfo;
245 QStack<Item> conns;
247 QMutex connMutex;
248 QSemaphore sem;
249 QTimer *expirationTimer = nullptr;
250
251};
252
253
271template <typename T, typename T_Group>
273{
274 public:
275
276 typedef QMap<QString, T_Group *> T_Groups;
277
279 {
280 mMutex.lock();
281 for ( T_Group *group : std::as_const( mGroups ) )
282 {
283 delete group;
284 }
285 mGroups.clear();
286 mMutex.unlock();
287 }
288
299 T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false, QgsFeedback *feedback = nullptr )
300 {
301 mMutex.lock();
302 typename T_Groups::iterator it = mGroups.find( connInfo );
303 if ( it == mGroups.end() )
304 {
305 it = mGroups.insert( connInfo, new T_Group( connInfo ) );
306 }
307 T_Group *group = *it;
308 mMutex.unlock();
309
310 if ( feedback )
311 {
312 QElapsedTimer timer;
313 timer.start();
314
315 while ( !feedback->isCanceled() )
316 {
317 if ( T conn = group->acquire( 300, requestMayBeNested ) )
318 return conn;
319
320 if ( timeout > 0 && timer.elapsed() >= timeout )
321 return nullptr;
322 }
323 return nullptr;
324 }
325 else
326 {
327 return group->acquire( timeout, requestMayBeNested );
328 }
329 }
330
332 void releaseConnection( T conn )
333 {
334 mMutex.lock();
335 typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
336 Q_ASSERT( it != mGroups.end() );
337 T_Group *group = *it;
338 mMutex.unlock();
339
340 group->release( conn );
341 }
342
350 void invalidateConnections( const QString &connInfo )
351 {
352 mMutex.lock();
353 if ( mGroups.contains( connInfo ) )
354 mGroups[connInfo]->invalidateConnections();
355 mMutex.unlock();
356 }
357
358
359 protected:
361 QMutex mMutex;
362};
363
364
365#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
T acquire(int timeout, bool requestMayBeNested)
Try to acquire a connection for a maximum of timeout milliseconds.
QgsConnectionPoolGroup(const QString &ci)
Constructor for QgsConnectionPoolGroup, with the specified connection info.
QgsConnectionPoolGroup & operator=(const QgsConnectionPoolGroup &other)=delete
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.
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:44
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