diff --git a/NEWS.md b/NEWS.md index 790200b9..e1b710e1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,8 @@ Enhancements: - Improve speed of instanciating a map (#147, #159, #165) - Give meaningful error on attempts to use map tokens with attribute service (#156) + - Reduce sql-api communication timeout, and allow overriding (#167) + [ new sqlapi.timeout directive, defaults to 100 ms ] 1.8.2 -- 2014-02-25 ------------------- diff --git a/config/environments/development.js.example b/config/environments/development.js.example index ec1feba4..5b0d8b28 100644 --- a/config/environments/development.js.example +++ b/config/environments/development.js.example @@ -106,7 +106,10 @@ var config = { // Maximum lenght of SQL query for GET // requests. Longer queries will be sent // using POST. Defaults to 2048 - max_get_sql_length: 2048 + max_get_sql_length: 2048, + // Maximum time to wait for a response, + // in milliseconds. Defaults to 100. + timeout: 100 } ,varnish: { host: 'localhost', diff --git a/config/environments/production.js.example b/config/environments/production.js.example index fab36404..51be65c1 100644 --- a/config/environments/production.js.example +++ b/config/environments/production.js.example @@ -100,7 +100,10 @@ var config = { // Maximum lenght of SQL query for GET // requests. Longer queries will be sent // using POST. Defaults to 2048 - max_get_sql_length: 2048 + max_get_sql_length: 2048, + // Maximum time to wait for a response, + // in milliseconds. Defaults to 100. + timeout: 100 } ,varnish: { host: 'localhost', diff --git a/config/environments/staging.js.example b/config/environments/staging.js.example index 35f356d0..15cffff9 100644 --- a/config/environments/staging.js.example +++ b/config/environments/staging.js.example @@ -100,7 +100,10 @@ var config = { // Maximum lenght of SQL query for GET // requests. Longer queries will be sent // using POST. Defaults to 2048 - max_get_sql_length: 2048 + max_get_sql_length: 2048, + // Maximum time to wait for a response, + // in milliseconds. Defaults to 100. + timeout: 100 } ,varnish: { host: 'localhost', diff --git a/config/environments/test.js.example b/config/environments/test.js.example index e071d35b..c282985c 100644 --- a/config/environments/test.js.example +++ b/config/environments/test.js.example @@ -102,7 +102,10 @@ var config = { // Maximum lenght of SQL query for GET // requests. Longer queries will be sent // using POST. Defaults to 2048 - max_get_sql_length: 2048 + max_get_sql_length: 2048, + // Maximum time to wait for a response, + // in milliseconds. Defaults to 100. + timeout: 100 } ,varnish: { host: '', diff --git a/lib/cartodb/server_options.js b/lib/cartodb/server_options.js index 6888700a..c7df43c8 100644 --- a/lib/cartodb/server_options.js +++ b/lib/cartodb/server_options.js @@ -116,13 +116,15 @@ module.exports = function(){ // var maxSockets = global.environment.maxConnections || 128; var maxGetLen = api.max_get_sql_length || 2048; + var maxSQLTime = api.timeout || 100; // 1/10 of a second by default var reqSpec = { url:sqlapi, json:true, headers:{host: sqlapihostname} // http://nodejs.org/api/http.html#http_agent_maxsockets ,pool:{maxSockets:maxSockets} - //,timeout:100 + // timeout in milliseconds + ,timeout:maxSQLTime } if ( sql.length > maxGetLen ) { reqSpec.method = 'POST'; diff --git a/test/acceptance/multilayer.js b/test/acceptance/multilayer.js index 716c965d..10f55f0c 100644 --- a/test/acceptance/multilayer.js +++ b/test/acceptance/multilayer.js @@ -1128,6 +1128,47 @@ suite('multilayer', function() { ); }); + // See https://github.com/CartoDB/Windshaft-cartodb/issues/167 + test("lack of response from sql-api will result in a timeout", function(done) { + + var layergroup = { + version: '1.0.0', + layers: [ + { options: { + sql: "select *, 'SQLAPINOANSWER' from test_table", + cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }', + cartocss_version: '2.1.0' + } } + ] + }; + + Step( + function do_post() + { + var next = this; + assert.response(server, { + url: '/tiles/layergroup', + method: 'POST', + headers: {host: 'localhost', 'Content-Type': 'application/json' }, + data: JSON.stringify(layergroup) + }, {}, function(res, err) { next(err, res); }); + }, + function check_post(err, res) { + if ( err ) throw err; + assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body); + var parsed = JSON.parse(res.body); + assert.ok(parsed.errors, 'Missing "errors" in response: ' + JSON.stringify(parsed)); + assert.equal(parsed.errors.length, 1); + var msg = parsed.errors[0]; + assert.equal(msg, 'Error: could not fetch source tables: ETIMEDOUT'); + return null; + }, + function finish(err) { + done(err); + } + ); + }); + suiteTeardown(function(done) { diff --git a/test/support/SQLAPIEmu.js b/test/support/SQLAPIEmu.js index f2411a5f..9bb52a9c 100644 --- a/test/support/SQLAPIEmu.js +++ b/test/support/SQLAPIEmu.js @@ -42,6 +42,9 @@ o.prototype.handleQuery = function(query, res) { if ( query.q.match('SQLAPIERROR') ) { res.statusCode = 400; res.write(JSON.stringify({'error':'Some error occurred'})); + } else if ( query.q.match('SQLAPINOANSWER') ) { + console.log("SQLAPIEmulator will never respond, on request"); + return; } else if ( query.q.match('EPOCH.* as max') ) { // This is the structure of the known query sent by tiler var row = {