From 7d2d2f951a034ddf9ef1e2f2c987023293d18a89 Mon Sep 17 00:00:00 2001 From: Lloyd Hilaiel <lloyd@hilaiel.com> Date: Fri, 30 Dec 2011 18:32:46 -0700 Subject: [PATCH] implement a 'maximum request time' for bcrypt work. server now fails more gracefully under backbreaking load, returning 503s to clients of the authenticate_user api. update loadgen to be less dramatic about, but still display, 503 errors. first part of issue #787 - a partial fix for #785 in dere too --- bin/load_gen | 21 ++++++++++++++++----- lib/bcrypt.js | 6 ++++-- lib/configuration.js | 9 ++++++--- lib/load_gen/activities/add_email.js | 1 + lib/load_gen/user_db.js | 4 ++++ lib/wsapi/authenticate_user.js | 5 +++++ package.json | 2 +- 7 files changed, 37 insertions(+), 11 deletions(-) diff --git a/bin/load_gen b/bin/load_gen index 5eb1ea00b..c8ad67f96 100755 --- a/bin/load_gen +++ b/bin/load_gen @@ -231,18 +231,27 @@ function poll() { if (activitiesToRun.indexOf(act) !== -1) { outstanding[act]++; activity[act].startFunc(configuration, function(err) { - if (err) winston.error(err); outstanding[act]--; - if (undefined === completed[act]) completed[act] = [ 0, 0 ]; - completed[act][err ? 1 : 0]++; + if (undefined === completed[act]) completed[act] = [ 0, 0, 0 ]; + if (err) { + if (err.indexOf('server is too busy') != -1) { + completed[act][2]++; + } else { + completed[act][1]++; + winston.error(err); + } + } else { + completed[act][0]++; + } }); } else { - if (undefined === completed[act]) completed[act] = [ 0, 0 ]; + if (undefined === completed[act]) completed[act] = [ 0, 0, 0 ]; completed[act][0]++; } } var numErrors = 0; + var num503s = 0; var numStarted = 0; function updateAverages(elapsed) { @@ -252,6 +261,7 @@ function poll() { Object.keys(completed).forEach(function(k) { numActCompleted += completed[k][0]; numErrors += completed[k][1]; + num503s += completed[k][2]; }); completed = { }; var avgUsersThisPeriod = (numActCompleted / activitiesPerUserPerSecond) * (elapsed / 1000); @@ -279,7 +289,8 @@ function poll() { "\t", averages[1].toFixed(2), "\t", averages[2].toFixed(2), "\t", actSumString, - "\t", numErrors ? "(" + numErrors + " ERRORS!)" : ""); + "\t", numErrors ? "(" + numErrors + " ERRORS!)" : "", + "\t", num503s ? " (" + num503s + " 503s)" : ""); } // ** how much time has elapsed since the last poll? diff --git a/lib/bcrypt.js b/lib/bcrypt.js index a9c679b61..0c613b147 100644 --- a/lib/bcrypt.js +++ b/lib/bcrypt.js @@ -1,11 +1,13 @@ const computecluster = require('compute-cluster'), logger = require('../lib/logging.js').logger, -bcrypt = require('bcrypt'); +bcrypt = require('bcrypt'), +config = require('./configuration.js'); var cc = new computecluster({ module: path.join(__dirname, "bcrypt-compute.js"), - max_backlog: 100000 + max_backlog: 100000, + max_request_time: config.get('max_compute_duration') }); cc.on('error', function(e) { diff --git a/lib/configuration.js b/lib/configuration.js index 4fb0d277b..92921fcdc 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -105,8 +105,10 @@ g_configs.production = { authentication_duration_ms: (2 * 7 * 24 * 60 * 60 * 1000), certificate_validity_ms: (24 * 60 * 60 * 1000), min_time_between_emails_ms: (60 * 1000), - // may be specified to manipulate the maximum number of compute - max_compute_processes: undefined + // may be specified to manipulate the maximum number of compute processes + max_compute_processes: undefined, + // return a 503 if a compute process would take over 10s to complete + max_compute_duration: 10 }; @@ -124,7 +126,8 @@ g_configs.local = { authentication_duration_ms: g_configs.production.authentication_duration_ms, certificate_validity_ms: g_configs.production.certificate_validity_ms, min_time_between_emails_ms: g_configs.production.min_time_between_emails_ms, - max_compute_processes: undefined + max_compute_processes: undefined, + max_compute_duration: 10 }; // test environments are variations on local diff --git a/lib/load_gen/activities/add_email.js b/lib/load_gen/activities/add_email.js index d059fdba7..3e5e34e50 100644 --- a/lib/load_gen/activities/add_email.js +++ b/lib/load_gen/activities/add_email.js @@ -67,6 +67,7 @@ exports.startFunc = function(cfg, cb) { cb = (function() { var _cb = cb; return function(x) { + if (x) userdb.removeLastEmailFromUser(user); userdb.releaseUser(user); _cb(x); }; diff --git a/lib/load_gen/user_db.js b/lib/load_gen/user_db.js index c6ceff78b..3e15e19ef 100644 --- a/lib/load_gen/user_db.js +++ b/lib/load_gen/user_db.js @@ -138,6 +138,10 @@ exports.addEmailToUser = function(user) { return email; }; +exports.removeLastEmailFromUser = function(user) { + user.emails.pop(); +}; + exports.addKeyToUserCtx = function(ctx, email) { // this is simulated. it will need to be real to apply load to // the verifier, but that in turn will drastically increase the diff --git a/lib/wsapi/authenticate_user.js b/lib/wsapi/authenticate_user.js index 4749c8f5a..ae49c96af 100644 --- a/lib/wsapi/authenticate_user.js +++ b/lib/wsapi/authenticate_user.js @@ -38,6 +38,11 @@ exports.process = function(req, res) { statsd.timing('bcrypt.compare_time', reqTime); if (err) { + if (err.indexOf('exceeded') != -1) { + logger.warn("max load hit, failing on auth request with 503: " + err); + res.status(503); + return fail("server is too busy"); + } logger.error("error comparing passwords with bcrypt: " + err); return fail("internal password check error"); } else if (!success) { diff --git a/package.json b/package.json index ef87aafb0..b5550b9fa 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ , "dependencies": { "JSONSelect": "0.2.1" , "bcrypt": "0.4.1" - , "compute-cluster": "0.0.5" + , "compute-cluster": "0.0.6" , "connect": "1.7.2" , "connect-cookie-session" : "0.0.2" , "connect-logger-statsd": "0.0.1" -- GitLab