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(); 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() virtual void close()
{ {
if (!is_closed_) if (!is_closed_)
@ -62,6 +74,10 @@ public:
is_closed_ = true; is_closed_ = true;
if (conn_) if (conn_)
{ {
if(conn_->isPending())
{
abort();
}
conn_.reset(); conn_.reset();
} }
} }

View file

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

View file

@ -701,6 +701,63 @@ if 'postgis' in mapnik.DatasourceCache.plugin_names() \
count += 1 count += 1
eq_(count,8) 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) atexit.register(postgis_takedown)
if __name__ == "__main__": if __name__ == "__main__":