Merge pull request #1068 from CartoDB/dynamic-map-pool
Handle 'max waitingClients count exceeded' error as: "429 You are over platfor's limits"
This commit is contained in:
commit
ec952d88cc
5
NEWS.md
5
NEWS.md
@ -2,7 +2,7 @@
|
||||
|
||||
**Deprecation warning**: Next major release will drop support for `Node.js 6 LTS`, `npm 3.x` and `yarn`. You'll be able to use the latest ES features as soon as we release 7.0.0. In the meantime, as a developer, you should keep compatibility with Node.js 6 LTS and keep updated both `package-lock.json` and `yarn.lock` files.
|
||||
|
||||
## 6.5.2
|
||||
## 6.6.0
|
||||
Released 2018-mm-dd
|
||||
|
||||
Announcements:
|
||||
@ -14,7 +14,8 @@ Announcements:
|
||||
- Update dev deps:
|
||||
- jshint@2.9.7
|
||||
- mocha@5.2.0
|
||||
|
||||
- Be able to customize max waiting workers parameter
|
||||
- Handle 'max waitingClients count exceeded' error as "429, You are over platfor's limits"
|
||||
|
||||
## 6.5.1
|
||||
Released 2018-12-26
|
||||
|
@ -139,6 +139,10 @@ var config = {
|
||||
// Important: check the configuration of uv_threadpool_size to use suitable value
|
||||
poolSize: 8,
|
||||
|
||||
// The maximum number of waiting clients of the pool of internal mapnik backend
|
||||
// This maximum number is per mapnik renderer created in Windshaft's RendererFactory
|
||||
poolMaxWaitingClients: 64,
|
||||
|
||||
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
|
||||
// This will prevent blocking the main thread.
|
||||
useCartocssWorkers: false,
|
||||
|
@ -139,6 +139,10 @@ var config = {
|
||||
// Important: check the configuration of uv_threadpool_size to use suitable value
|
||||
poolSize: 8,
|
||||
|
||||
// The maximum number of waiting clients of the pool of internal mapnik backend
|
||||
// This maximum number is per mapnik renderer created in Windshaft's RendererFactory
|
||||
poolMaxWaitingClients: 64,
|
||||
|
||||
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
|
||||
// This will prevent blocking the main thread.
|
||||
useCartocssWorkers: false,
|
||||
|
@ -139,6 +139,10 @@ var config = {
|
||||
// Important: check the configuration of uv_threadpool_size to use suitable value
|
||||
poolSize: 8,
|
||||
|
||||
// The maximum number of waiting clients of the pool of internal mapnik backend
|
||||
// This maximum number is per mapnik renderer created in Windshaft's RendererFactory
|
||||
poolMaxWaitingClients: 64,
|
||||
|
||||
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
|
||||
// This will prevent blocking the main thread.
|
||||
useCartocssWorkers: false,
|
||||
|
@ -139,6 +139,10 @@ var config = {
|
||||
// Important: check the configuration of uv_threadpool_size to use suitable value
|
||||
poolSize: 8,
|
||||
|
||||
// The maximum number of waiting clients of the pool of internal mapnik backend
|
||||
// This maximum number is per mapnik renderer created in Windshaft's RendererFactory
|
||||
poolMaxWaitingClients: 64,
|
||||
|
||||
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
|
||||
// This will prevent blocking the main thread.
|
||||
useCartocssWorkers: false,
|
||||
|
@ -9,7 +9,7 @@ module.exports = function errorMiddleware (/* options */) {
|
||||
// jshint maxcomplexity:9
|
||||
var allErrors = Array.isArray(err) ? err : [err];
|
||||
|
||||
allErrors = populateTimeoutErrors(allErrors);
|
||||
allErrors = populateLimitErrors(allErrors);
|
||||
|
||||
const label = err.label || 'UNKNOWN';
|
||||
err = allErrors[0] || new Error(label);
|
||||
@ -59,8 +59,22 @@ function getErrorTypes(error) {
|
||||
};
|
||||
}
|
||||
|
||||
function populateTimeoutErrors (errors) {
|
||||
function isMaxWaitingClientsError (err) {
|
||||
return err.message === 'max waitingClients count exceeded';
|
||||
}
|
||||
|
||||
function populateLimitErrors (errors) {
|
||||
return errors.map(function (error) {
|
||||
if (isMaxWaitingClientsError(error)) {
|
||||
error.message = 'You are over platform\'s limits: Max render capacity exceeded.' +
|
||||
' Contact CARTO support for more details.';
|
||||
error.type = 'limit';
|
||||
error.subtype = 'render-capacity';
|
||||
error.http_status = 429;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
const errorTypes = getErrorTypes(error);
|
||||
|
||||
if (isTimeoutError(errorTypes)) {
|
||||
|
@ -9,6 +9,7 @@ var rendererConfig = _.defaults(global.environment.renderer || {}, {
|
||||
statsInterval: 60000,
|
||||
mapnik: {
|
||||
poolSize: 8,
|
||||
poolMaxWaitingClients: 64,
|
||||
metatile: 2,
|
||||
bufferSize: 64,
|
||||
snapToGrid: false,
|
||||
|
97
package-lock.json
generated
97
package-lock.json
generated
@ -1946,9 +1946,9 @@
|
||||
}
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz",
|
||||
"integrity": "sha1-eAw29p360FpaBF3Te+etyhGk9v8="
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.2.tgz",
|
||||
"integrity": "sha1-eon0kdV1tC+fBpoOjixtuqPCQb4="
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "1.0.3",
|
||||
@ -2029,11 +2029,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.2.tgz",
|
||||
"integrity": "sha1-eon0kdV1tC+fBpoOjixtuqPCQb4="
|
||||
},
|
||||
"mapnik-reference": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mapnik-reference/-/mapnik-reference-8.5.6.tgz",
|
||||
@ -2582,13 +2577,6 @@
|
||||
"requires": {
|
||||
"generic-pool": "~2.2.1",
|
||||
"xtend": "~4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.2.tgz",
|
||||
"integrity": "sha1-eon0kdV1tC+fBpoOjixtuqPCQb4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapnik-reference": {
|
||||
@ -2639,6 +2627,11 @@
|
||||
"zipfile": "~0.5.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.6.tgz",
|
||||
"integrity": "sha1-8bVeVyFn26L+ddWqkeux6fcmQtc="
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
|
||||
@ -2895,9 +2888,9 @@
|
||||
}
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
|
||||
"integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.2.tgz",
|
||||
"integrity": "sha512-YcMnjqeoUckXTPKZSAsPjUPLxH85XotbpqK3w4RyCwdFQSU5FxxBys8buehkSfg0j9fKvV1hn7O0+8reEgkAiw==",
|
||||
"requires": {
|
||||
"hosted-git-info": "^2.1.4",
|
||||
"is-builtin-module": "^1.0.0",
|
||||
@ -2949,9 +2942,9 @@
|
||||
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz",
|
||||
"integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A="
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"object-keys": {
|
||||
"version": "0.4.0",
|
||||
@ -3163,6 +3156,18 @@
|
||||
"requires": {
|
||||
"generic-pool": "2.4.3",
|
||||
"object-assign": "4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz",
|
||||
"integrity": "sha1-eAw29p360FpaBF3Te+etyhGk9v8="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz",
|
||||
"integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A="
|
||||
}
|
||||
}
|
||||
},
|
||||
"pg-types": {
|
||||
@ -3926,27 +3931,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tilelive-mapnik": {
|
||||
"version": "github:cartodb/tilelive-mapnik#30b5d9f4f6b774d084bc6dc67a7a01d51e129bb5",
|
||||
"from": "github:cartodb/tilelive-mapnik#0.6.18-cdb16",
|
||||
"requires": {
|
||||
"@carto/mapnik": "3.6.2-carto.11",
|
||||
"generic-pool": "2.5.4",
|
||||
"mime": "2.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.5.4.tgz",
|
||||
"integrity": "sha1-OMYYhRPhQDCUjsblz2VSPZd5KZs="
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
|
||||
"integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"torque.js": {
|
||||
"version": "2.17.1",
|
||||
"resolved": "https://registry.npmjs.org/torque.js/-/torque.js-2.17.1.tgz",
|
||||
@ -4149,9 +4133,9 @@
|
||||
"integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU="
|
||||
},
|
||||
"windshaft": {
|
||||
"version": "4.12.3",
|
||||
"resolved": "https://registry.npmjs.org/windshaft/-/windshaft-4.12.3.tgz",
|
||||
"integrity": "sha512-Jay1yXFVmK8BK6EOoJ27xhCIFOaqYkGxKYlAQyJmxYAULhqNLeyiEk3qxcVUyLD0vv0GzmUiHvJQq1ojYnPMGA==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/windshaft/-/windshaft-4.13.0.tgz",
|
||||
"integrity": "sha512-o9Zmv8bwnFj7lPRMhd6sGC7+VNg+pX9pbha5U7tcpu42sKmYMZl1zrpMJMS6fzAW8JfKa2aXALZ8WrSS7Cue2A==",
|
||||
"requires": {
|
||||
"@carto/mapnik": "3.6.2-carto.11",
|
||||
"@carto/tilelive-bridge": "github:cartodb/tilelive-bridge#e35ae36a6e2d555a6b312440f7e1904c5ad03664",
|
||||
@ -4168,9 +4152,30 @@
|
||||
"semver": "5.5.0",
|
||||
"sphericalmercator": "1.0.5",
|
||||
"tilelive": "5.12.3",
|
||||
"tilelive-mapnik": "github:cartodb/tilelive-mapnik#30b5d9f4f6b774d084bc6dc67a7a01d51e129bb5",
|
||||
"tilelive-mapnik": "github:cartodb/tilelive-mapnik#a16ae79202d1b4288c1b0fe28c5e939e3be6b594",
|
||||
"torque.js": "2.17.1",
|
||||
"underscore": "1.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.5.0.tgz",
|
||||
"integrity": "sha512-dEkxmX+egB2o4NR80c/q+xzLLzLX+k68/K8xv81XprD+Sk7ZtP14VugeCz+fUwv5FzpWq40pPtAkzPRqT8ka9w=="
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
|
||||
"integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg=="
|
||||
},
|
||||
"tilelive-mapnik": {
|
||||
"version": "github:cartodb/tilelive-mapnik#a16ae79202d1b4288c1b0fe28c5e939e3be6b594",
|
||||
"from": "github:cartodb/tilelive-mapnik#0.6.18-cdb17",
|
||||
"requires": {
|
||||
"@carto/mapnik": "3.6.2-carto.11",
|
||||
"generic-pool": "3.5.0",
|
||||
"mime": "2.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"wordwrap": {
|
||||
|
@ -49,7 +49,7 @@
|
||||
"step-profiler": "0.3.0",
|
||||
"turbo-carto": "0.21.0",
|
||||
"underscore": "1.6.0",
|
||||
"windshaft": "^4.12.3",
|
||||
"windshaft": "^4.13.0",
|
||||
"yargs": "11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
128
test/acceptance/max-waiting-workers.js
Normal file
128
test/acceptance/max-waiting-workers.js
Normal file
@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
require('../support/test_helper');
|
||||
|
||||
const assert = require('../support/assert');
|
||||
const TestClient = require('../support/test-client');
|
||||
const createMapConfig = ({
|
||||
version = '1.8.0',
|
||||
type = 'cartodb',
|
||||
sql = TestClient.SQL.ONE_POINT,
|
||||
cartocss = TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version = '2.3.0',
|
||||
interactivity = 'cartodb_id'
|
||||
} = {}) => ({
|
||||
version,
|
||||
layers: [{
|
||||
type,
|
||||
options: {
|
||||
source: {
|
||||
id: 'a0'
|
||||
},
|
||||
cartocss,
|
||||
cartocss_version,
|
||||
interactivity
|
||||
}
|
||||
}],
|
||||
analyses: [
|
||||
{
|
||||
id: 'a0',
|
||||
type: 'source',
|
||||
params: {
|
||||
query: sql
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
const coords = [
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 0, 1],
|
||||
[1, 1, 0],
|
||||
[1, 1, 1],
|
||||
[2, 0, 0],
|
||||
[2, 0, 1],
|
||||
[2, 0, 2],
|
||||
[2, 0, 3],
|
||||
[2, 1, 0],
|
||||
[2, 1, 1],
|
||||
[2, 1, 2],
|
||||
[2, 1, 3],
|
||||
[2, 2, 0],
|
||||
[2, 2, 1],
|
||||
[2, 2, 2],
|
||||
[2, 2, 3],
|
||||
[2, 3, 0],
|
||||
[2, 3, 1],
|
||||
[2, 3, 2],
|
||||
[2, 3, 3]
|
||||
];
|
||||
|
||||
function getTiles ({ testClient, layergroupid, coords }) {
|
||||
return Promise.all(coords.map((coord) => getTile({ testClient, layergroupid, coord })));
|
||||
}
|
||||
|
||||
function getTile ({ testClient, layergroupid, coord }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const [ z, x, y ] = coord;
|
||||
const params = {
|
||||
layergroupid,
|
||||
format: 'png',
|
||||
response: {
|
||||
status: [ 200, 429 ],
|
||||
headers: {
|
||||
'Content-Type': /^(image\/png|application\/json; charset=utf-8)$/
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
testClient.getTile(z, x, y, params, (err, res, tile) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve({ res, tile });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('exceeding max waiting workers', function () {
|
||||
const originalPoolSize = global.environment.renderer.mapnik.poolSize;
|
||||
const poolMaxWaitingClients = global.environment.renderer.mapnik.poolMaxWaitingClients;
|
||||
const apikey = 1234;
|
||||
const testClient = new TestClient(createMapConfig(), apikey);
|
||||
let layergroupid;
|
||||
|
||||
before(function (done) {
|
||||
global.environment.renderer.mapnik.poolSize = 1;
|
||||
global.environment.renderer.mapnik.poolMaxWaitingClients = 1;
|
||||
|
||||
testClient.getLayergroup({ status: 200 }, (err, res) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
layergroupid = res.layergroupid;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function () {
|
||||
global.environment.renderer.mapnik.poolSize = originalPoolSize;
|
||||
global.environment.renderer.mapnik.poolMaxWaitingClients = poolMaxWaitingClients;
|
||||
});
|
||||
|
||||
it('should get 429: You are over platform\'s limits', function (done) {
|
||||
const testClient = new TestClient(createMapConfig(), apikey);
|
||||
|
||||
getTiles({ testClient, layergroupid, coords })
|
||||
.then((results) => {
|
||||
const errs = results
|
||||
.filter(({ res }) => res.headers['content-type'] === 'application/json; charset=utf-8')
|
||||
.filter(({ tile }) => tile.errors && tile.errors_with_context[0].subtype === 'render-capacity');
|
||||
|
||||
assert.ok(errs.length > 0);
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
@ -142,16 +142,18 @@ function validateResponseBody(response, expected) {
|
||||
|
||||
function validateResponseStatus(response, expected) {
|
||||
var status = expected.status || expected.statusCode;
|
||||
const message = colorize('[red]{Invalid response status code.}\n' +
|
||||
' Expected: [green]{' + status + '}\n' +
|
||||
' Got: [red]{' + response.statusCode + '}\n' +
|
||||
' Body: ' + response.body);
|
||||
|
||||
// Assert response status
|
||||
if (typeof status === 'number') {
|
||||
if (response.statusCode !== status) {
|
||||
return new Error(colorize(
|
||||
'[red]{Invalid response status code.}\n' +
|
||||
' Expected: [green]{' + status + '}\n' +
|
||||
' Got: [red]{' + response.statusCode + '}\n' +
|
||||
' Body: ' + response.body)
|
||||
);
|
||||
}
|
||||
if (typeof status === 'number' && response.statusCode !== status) {
|
||||
return new Error(message);
|
||||
}
|
||||
|
||||
if (Array.isArray(status) && !status.includes(response.statusCode)) {
|
||||
return new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user