Skip to content

Commit 881b1cc

Browse files
committed
Added connection pool for SpatiaLite, improving rendering speed within one DB
- refactored PostgreSQL connection pool into generic template classes - used the template classes for SpatiaLite connection pool - pooled SpatiaLite connections are opened with SQLITE3_OPEN_NOMUTEX flag to allow parallel access
1 parent 2078220 commit 881b1cc

12 files changed

+534
-366
lines changed

src/core/qgsconnectionpool.h

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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+
27+
#define CONN_POOL_MAX_CONCURRENT_CONNS 4
28+
#define CONN_POOL_EXPIRATION_TIME 60 // in seconds
29+
30+
31+
/*! Template that stores data related to one server.
32+
*
33+
* It is assumed that following functions exist:
34+
* - void qgsConnectionPool_ConnectionCreate(QString name, T& c) ... create a new connection
35+
* - void qgsConnectionPool_ConnectionDestroy(T c) ... destroy the connection
36+
* - QString qgsConnectionPool_ConnectionToName(T c) ... lookup connection's name (path)
37+
*
38+
* Because of issues with templates and QObject's signals and slots, this class only provides helper functions for QObject-related
39+
* functionality - the place which uses the template is resonsible for:
40+
* - being derived from QObject
41+
* - calling initTimer( this ) in constructor
42+
* - having handleConnectionExpired() slot that calls onConnectionExpired()
43+
*/
44+
template <typename T>
45+
class QgsConnectionPoolGroup
46+
{
47+
public:
48+
49+
static const int maxConcurrentConnections;
50+
51+
struct Item
52+
{
53+
T c;
54+
QTime lastUsedTime;
55+
};
56+
57+
QgsConnectionPoolGroup( const QString& ci )
58+
: connInfo( ci )
59+
, sem( CONN_POOL_MAX_CONCURRENT_CONNS )
60+
, expirationTimer( 0 )
61+
{
62+
}
63+
64+
~QgsConnectionPoolGroup()
65+
{
66+
foreach ( Item item, conns )
67+
{
68+
qgsConnectionPool_ConnectionDestroy( item.c );
69+
}
70+
}
71+
72+
T acquire()
73+
{
74+
// we are going to acquire a resource - if no resource is available, we will block here
75+
sem.acquire();
76+
77+
// quick (preferred) way - use cached connection
78+
{
79+
QMutexLocker locker( &connMutex );
80+
81+
if ( !conns.isEmpty() )
82+
{
83+
Item i = conns.pop();
84+
85+
// no need to run if nothing can expire
86+
if ( conns.isEmpty() )
87+
expirationTimer->stop();
88+
89+
return i.c;
90+
}
91+
}
92+
93+
T c;
94+
qgsConnectionPool_ConnectionCreate( connInfo, c );
95+
if ( !c )
96+
{
97+
// we didn't get connection for some reason, so release the lock
98+
sem.release();
99+
return 0;
100+
}
101+
102+
return c;
103+
}
104+
105+
void release( T conn )
106+
{
107+
connMutex.lock();
108+
Item i;
109+
i.c = conn;
110+
i.lastUsedTime = QTime::currentTime();
111+
conns.push( i );
112+
113+
if ( !expirationTimer->isActive() )
114+
expirationTimer->start();
115+
116+
connMutex.unlock();
117+
118+
sem.release(); // this can unlock a thread waiting in acquire()
119+
}
120+
121+
protected:
122+
123+
void initTimer( QObject* parent )
124+
{
125+
expirationTimer = new QTimer( parent );
126+
expirationTimer->setInterval( CONN_POOL_EXPIRATION_TIME * 1000 );
127+
QObject::connect( expirationTimer, SIGNAL( timeout() ), parent, SLOT( handleConnectionExpired() ) );
128+
129+
// just to make sure the object belongs to main thread and thus will get events
130+
parent->moveToThread( qApp->thread() );
131+
}
132+
133+
void onConnectionExpired()
134+
{
135+
connMutex.lock();
136+
137+
QTime now = QTime::currentTime();
138+
139+
// what connections have expired?
140+
QList<int> toDelete;
141+
for ( int i = 0; i < conns.count(); ++i )
142+
{
143+
if ( conns.at( i ).lastUsedTime.secsTo( now ) >= CONN_POOL_EXPIRATION_TIME )
144+
toDelete.append( i );
145+
}
146+
147+
// delete expired connections
148+
for ( int j = toDelete.count() - 1; j >= 0; --j )
149+
{
150+
int index = toDelete[j];
151+
qgsConnectionPool_ConnectionDestroy( conns[index].c );
152+
conns.remove( index );
153+
}
154+
155+
if ( conns.isEmpty() )
156+
expirationTimer->stop();
157+
158+
connMutex.unlock();
159+
}
160+
161+
protected:
162+
163+
QString connInfo;
164+
QStack<Item> conns;
165+
QMutex connMutex;
166+
QSemaphore sem;
167+
QTimer* expirationTimer;
168+
};
169+
170+
171+
/**
172+
* Template class responsible for keeping a pool of open connections.
173+
* This is desired to avoid the overhead of creation of new connection everytime.
174+
*
175+
* The methods are thread safe.
176+
*
177+
* The connection pool has a limit on maximum number of concurrent connections
178+
* (per server), once the limit is reached, the acquireConnection() function
179+
* will block. All connections that have been acquired must be then released
180+
* with releaseConnection() function.
181+
*
182+
* When the connections are not used for some time, they will get closed automatically
183+
* to save resources.
184+
*
185+
*/
186+
template <typename T, typename T_Group>
187+
class QgsConnectionPool
188+
{
189+
public:
190+
191+
typedef QMap<QString, T_Group*> T_Groups;
192+
193+
//! Try to acquire a connection: if no connections are available, the thread will get blocked.
194+
//! @return initialized connection or null on error
195+
T acquireConnection( const QString& connInfo )
196+
{
197+
mMutex.lock();
198+
typename T_Groups::iterator it = mGroups.find( connInfo );
199+
if ( it == mGroups.end() )
200+
{
201+
it = mGroups.insert( connInfo, new T_Group( connInfo ) );
202+
}
203+
T_Group* group = *it;
204+
mMutex.unlock();
205+
206+
return group->acquire();
207+
}
208+
209+
//! Release an existing connection so it will get back into the pool and can be reused
210+
void releaseConnection( T conn )
211+
{
212+
mMutex.lock();
213+
typename T_Groups::iterator it = mGroups.find( qgsConnectionPool_ConnectionToName( conn ) );
214+
Q_ASSERT( it != mGroups.end() );
215+
T_Group* group = *it;
216+
mMutex.unlock();
217+
218+
group->release( conn );
219+
}
220+
221+
protected:
222+
T_Groups mGroups;
223+
QMutex mMutex;
224+
225+
};
226+
227+
228+
#endif // QGSCONNECTIONPOOL_H

src/providers/postgres/qgspostgresconnpool.cpp

Lines changed: 0 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -17,115 +17,6 @@
1717

1818
#include "qgspostgresconn.h"
1919

20-
#include <QCoreApplication>
21-
22-
#define POSTGRES_MAX_CONCURRENT_CONNS 4
23-
#define POSTGRES_CONN_EXPIRATION 60 // in seconds
24-
25-
26-
27-
QgsPostgresConnPoolGroup::QgsPostgresConnPoolGroup( const QString& ci )
28-
: connInfo( ci )
29-
, sem( POSTGRES_MAX_CONCURRENT_CONNS )
30-
, expirationTimer( this )
31-
{
32-
// just to make sure the object belongs to main thread and thus will get events
33-
moveToThread( qApp->thread() );
34-
35-
expirationTimer.setInterval( POSTGRES_CONN_EXPIRATION * 1000 );
36-
connect( &expirationTimer, SIGNAL( timeout() ), this, SLOT( handleConnectionExpired() ) );
37-
}
38-
39-
40-
QgsPostgresConnPoolGroup::~QgsPostgresConnPoolGroup()
41-
{
42-
foreach ( Item item, conns )
43-
item.c->disconnect();
44-
}
45-
46-
47-
QgsPostgresConn* QgsPostgresConnPoolGroup::acquire()
48-
{
49-
// we are going to acquire a resource - if no resource is available, we will block here
50-
sem.acquire();
51-
52-
// quick (preferred) way - use cached connection
53-
{
54-
QMutexLocker locker( &connMutex );
55-
56-
if ( !conns.isEmpty() )
57-
{
58-
Item i = conns.pop();
59-
60-
// no need to run if nothing can expire
61-
if ( conns.isEmpty() )
62-
expirationTimer.stop();
63-
64-
return i.c;
65-
}
66-
}
67-
68-
QgsPostgresConn* c = QgsPostgresConn::connectDb( connInfo, true, false ); // TODO: read-only
69-
if ( !c )
70-
{
71-
// we didn't get connection for some reason, so release the lock
72-
sem.release();
73-
return 0;
74-
}
75-
76-
return c;
77-
}
78-
79-
80-
void QgsPostgresConnPoolGroup::release( QgsPostgresConn* conn )
81-
{
82-
connMutex.lock();
83-
Item i;
84-
i.c = conn;
85-
i.lastUsedTime = QTime::currentTime();
86-
conns.push( i );
87-
88-
if ( !expirationTimer.isActive() )
89-
expirationTimer.start();
90-
91-
connMutex.unlock();
92-
93-
sem.release(); // this can unlock a thread waiting in acquire()
94-
}
95-
96-
97-
void QgsPostgresConnPoolGroup::handleConnectionExpired()
98-
{
99-
connMutex.lock();
100-
101-
QTime now = QTime::currentTime();
102-
103-
// what connections have expired?
104-
QList<int> toDelete;
105-
for ( int i = 0; i < conns.count(); ++i )
106-
{
107-
if ( conns.at( i ).lastUsedTime.secsTo( now ) >= POSTGRES_CONN_EXPIRATION )
108-
toDelete.append( i );
109-
}
110-
111-
// delete expired connections
112-
for ( int j = toDelete.count() - 1; j >= 0; --j )
113-
{
114-
int index = toDelete[j];
115-
conns[index].c->disconnect();
116-
conns.remove( index );
117-
}
118-
119-
if ( conns.isEmpty() )
120-
expirationTimer.stop();
121-
122-
connMutex.unlock();
123-
}
124-
125-
126-
// ----
127-
128-
12920
QgsPostgresConnPool* QgsPostgresConnPool::mInstance = 0;
13021

13122

@@ -135,30 +26,3 @@ QgsPostgresConnPool* QgsPostgresConnPool::instance()
13526
mInstance = new QgsPostgresConnPool;
13627
return mInstance;
13728
}
138-
139-
140-
QgsPostgresConn* QgsPostgresConnPool::acquireConnection( const QString& connInfo )
141-
{
142-
mMutex.lock();
143-
QgsPostgresConnPoolGroups::iterator it = mGroups.find( connInfo );
144-
if ( it == mGroups.end() )
145-
{
146-
it = mGroups.insert( connInfo, new QgsPostgresConnPoolGroup( connInfo ) );
147-
}
148-
QgsPostgresConnPoolGroup* group = *it;
149-
mMutex.unlock();
150-
151-
return group->acquire();
152-
}
153-
154-
155-
void QgsPostgresConnPool::releaseConnection( QgsPostgresConn* conn )
156-
{
157-
mMutex.lock();
158-
QgsPostgresConnPoolGroups::iterator it = mGroups.find( conn->connInfo() );
159-
Q_ASSERT( it != mGroups.end() );
160-
QgsPostgresConnPoolGroup* group = *it;
161-
mMutex.unlock();
162-
163-
group->release( conn );
164-
}

0 commit comments

Comments
 (0)