Abort pending postgis connections when exception occurs

- patch from @abonnasseau
  - closes #2042
  - refs #2069
This commit is contained in:
Dane Springmeyer 2013-11-12 18:38:39 -08:00
parent 9c82c0c957
commit 6844863a89
3 changed files with 89 additions and 4 deletions

View file

@ -54,6 +54,18 @@ public:
close();
}
void abort()
{
if(conn_ && conn_->isPending() )
{
MAPNIK_LOG_DEBUG(postgis) << "AsyncResultSet: aborting pending connection - " << conn_.get();
// there is no easy way to abort a pending connection, so we close it : this will ensure that
// the connection will be recycled in the pool
conn_->close();
}
}
virtual void close()
{
if (!is_closed_)
@ -62,6 +74,10 @@ public:
is_closed_ = true;
if (conn_)
{
if(conn_->isPending())
{
abort();
}
conn_.reset();
}
}

View file

@ -46,7 +46,8 @@ class Connection
public:
Connection(std::string const& connection_str,boost::optional<std::string> const& password)
: cursorId(0),
closed_(false)
closed_(false),
pending_(false)
{
std::string connect_with_pass = connection_str;
if (password && !password->empty())
@ -54,6 +55,7 @@ public:
connect_with_pass += " password=" + *password;
}
conn_ = PQconnectdb(connect_with_pass.c_str());
MAPNIK_LOG_DEBUG(postgis) << "postgis_connection: postgresql connection create - " << this;
if (PQstatus(conn_) != CONNECTION_OK)
{
std::string err_msg = "Postgis Plugin: ";
@ -61,6 +63,8 @@ public:
err_msg += "\nConnection string: '";
err_msg += connection_str;
err_msg += "'\n";
MAPNIK_LOG_DEBUG(postgis) << "postgis_connection: creation failed, closing connection - " << this;
close();
throw mapnik::datasource_exception(err_msg);
}
}
@ -70,7 +74,7 @@ public:
if (! closed_)
{
PQfinish(conn_);
MAPNIK_LOG_DEBUG(postgis) << "postgis_connection: postgresql connection closed - " << conn_;
MAPNIK_LOG_DEBUG(postgis) << "postgis_connection: postgresql connection closed - " << this;
closed_ = true;
}
}
@ -156,6 +160,7 @@ public:
close();
throw mapnik::datasource_exception(err_msg);
}
pending_ = true;
return result;
}
@ -204,12 +209,17 @@ public:
return (!closed_) && (PQstatus(conn_) != CONNECTION_BAD);
}
bool isPending() const
{
return pending_;
}
void close()
{
if (! closed_)
{
PQfinish(conn_);
MAPNIK_LOG_DEBUG(postgis) << "postgis_connection: datasource closed, also closing connection - " << conn_;
MAPNIK_LOG_DEBUG(postgis) << "postgis_connection: closing connection (close)- " << this;
closed_ = true;
}
}
@ -225,8 +235,9 @@ private:
PGconn *conn_;
int cursorId;
bool closed_;
bool pending_;
void clearAsyncResult(PGresult *result) const
void clearAsyncResult(PGresult *result)
{
// Clear all pending results
while(result)
@ -234,6 +245,7 @@ private:
PQclear(result);
result = PQgetResult(conn_);
}
pending_ = false;
}
};

View file

@ -701,6 +701,63 @@ if 'postgis' in mapnik.DatasourceCache.plugin_names() \
count += 1
eq_(count,8)
def test_psql_error_should_give_back_connections_opened_for_lower_layers_to_the_pool():
map1 = mapnik.Map(600,300)
s = mapnik.Style()
r = mapnik.Rule()
r.symbols.append(mapnik.PolygonSymbolizer(mapnik.Color('#f2eff9')))
s.rules.append(r)
map1.append_style('style',s)
# This layer will fail after a while
buggy_s = mapnik.Style()
buggy_r = mapnik.Rule()
buggy_r.symbols.append(mapnik.PolygonSymbolizer(mapnik.Color('#ff0000')))
buggy_r.filter = mapnik.Filter("[fips] = 'FR'")
buggy_s.rules.append(buggy_r)
map1.append_style('style for buggy layer',buggy_s)
buggy_layer = mapnik.Layer('this layer is buggy at runtime')
# We ensure the query wille be long enough
buggy_layer.datasource = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='(SELECT geom as geom, pg_sleep(0.1), fips::int from world_merc) as failure_tabl',
max_async_connection=2, max_size=2,asynchronous_request = True, geometry_field='geom')
buggy_layer.styles.append('style for buggy layer')
# The query for this layer will be sent, then the previous layer will raise an exception before results are read
forced_canceled_layer = mapnik.Layer('this layer will be canceled when an exception stops map rendering')
forced_canceled_layer.datasource = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='world_merc',
max_async_connection=2, max_size=2, asynchronous_request = True, geometry_field='geom')
forced_canceled_layer.styles.append('style')
map1.layers.append(buggy_layer)
map1.layers.append(forced_canceled_layer)
map1.zoom_all()
map2 = mapnik.Map(600,300)
map2.background = mapnik.Color('steelblue')
s = mapnik.Style()
r = mapnik.Rule()
r.symbols.append(mapnik.LineSymbolizer(mapnik.Color('rgb(50%,50%,50%)'),0.1))
r.symbols.append(mapnik.LineSymbolizer(mapnik.Color('rgb(50%,50%,50%)'),0.1))
s.rules.append(r)
map2.append_style('style',s)
layer1 = mapnik.Layer('layer1')
layer1.datasource = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='world_merc',
max_async_connection=2, max_size=2, asynchronous_request = True, geometry_field='geom')
layer1.styles.append('style')
map2.layers.append(layer1)
map2.zoom_all()
# We expect this to trigger a PSQL error
try:
mapnik.render_to_file(map1,'world.png', 'png')
# Test must fail if error was not raised just above
eq_(False,True)
except RuntimeError:
pass
# This used to raise an exception before correction of issue 2042
mapnik.render_to_file(map2,'world2.png', 'png')
atexit.register(postgis_takedown)
if __name__ == "__main__":