From 58668a7744027e18ccb89765919938df2d5f22df Mon Sep 17 00:00:00 2001
From: Lloyd Hilaiel <lloyd@hilaiel.com>
Date: Wed, 30 Nov 2011 14:00:16 -0700
Subject: [PATCH] keysigner to saturate multiple cores - issue #213

---
 bin/keysigner               | 59 ++++++++++++++++++++++++-------------
 lib/configuration.js        | 13 ++++++--
 lib/keysigner/subprocess.js | 19 ++++++++++++
 package.json                |  1 +
 4 files changed, 70 insertions(+), 22 deletions(-)
 create mode 100644 lib/keysigner/subprocess.js

diff --git a/bin/keysigner b/bin/keysigner
index ff422d35c..f8b0e0585 100755
--- a/bin/keysigner
+++ b/bin/keysigner
@@ -47,9 +47,9 @@ httputils = require('../lib/httputils.js'),
 validate = require('../lib/validate.js'),
 metrics = require('../lib/metrics.js'),
 logger = require('../lib/logging.js').logger,
-ca = require('../lib/keysigner/ca.js'),
 heartbeat = require('../lib/heartbeat'),
-shutdown = require('../lib/shutdown');
+shutdown = require('../lib/shutdown'),
+computecluster = require('compute-cluster');
 
 // create an express server
 var app = express.createServer();
@@ -84,32 +84,51 @@ app.use(function(req, resp, next) {
 // parse POST bodies
 app.use(express.bodyParser());
 
+// allocate a compute cluster
+try {
+  var cc = new computecluster({
+    module: path.join(__dirname, "..", "lib", "keysigner", "subprocess.js"),
+    max_processes: config.get('max_compute_processes')
+  }).on('error', function(e) {
+    logger.error("error detected in keysigning computation process!  fatal: " + e.toString());
+    setTimeout(function() { process.exit(1); }, 0);
+  }).on('info', function(msg) {
+    logger.info("(compute cluster): " + msg);
+  }).on('debug', function(msg) {
+    logger.debug("(compute cluster): " + msg);
+  });
+} catch(e) {
+  process.stderr.write("can't allocate compute cluster: " + e + "\n");
+  process.exit(1);
+}
+
 // and our single function
 app.post('/wsapi/cert_key', validate(["email", "pubkey"]), function(req, resp) {
-  try {
-    // parse the pubkey
-    var pk = ca.parsePublicKey(req.body.pubkey);
-
-    // same account, we certify the key
-    // we certify it for a day for now
-    var expiration = new Date();
-    expiration.setTime(new Date().valueOf() + config.get('certificate_validity_ms'));
-    var cert = ca.certify(req.body.email, pk, expiration);
-
-    resp.writeHead(200, {'Content-Type': 'text/plain'});
-    resp.write(cert);
-    resp.end();
-  } catch (e) {
-    logger.error("certification generation error: " + e.toString());
-    httputils.serverError(resp, "certification generation error");
-  }
+  cc.enqueue({
+    pubkey: req.body.pubkey,
+    email: req.body.email
+  }, function (err, r) {
+    // consider "application" errors to be the same as harder errors
+    if (!err && r && r.error) err = r.error;
+    if (!r || !r.success) err = "no certificate returned from child process";
+    if (err) {
+      logger.error("certification generation error: " + err);
+      httputils.serverError(resp, "certification generation error");
+    } else {
+      resp.writeHead(200, {'Content-Type': 'text/plain'});
+      resp.write(r.success);
+      resp.end();
+    }
+  });
 });
 
 // shutdown when code_update is invoked
 shutdown.installUpdateHandler(app);
 
 // shutdown nicely on signals
-shutdown.handleTerminationSignals(app);
+shutdown.handleTerminationSignals(app, function() {
+  cc.exit();
+});
 
 var bindTo = config.get('bind_to');
 app.listen(bindTo.port, bindTo.host, function() {
diff --git a/lib/configuration.js b/lib/configuration.js
index acb6f0a43..d24973ca4 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -104,7 +104,9 @@ g_configs.production = {
   bcrypt_work_factor: 12,
   authentication_duration_ms: (2 * 7 * 24 * 60 * 60 * 1000),
   certificate_validity_ms: (24 * 60 * 60 * 1000),
-  min_time_between_emails_ms: (60 * 1000)
+  min_time_between_emails_ms: (60 * 1000),
+  // may be specified to manipulate the maximum number of compute
+  max_compute_processes: undefined
 };
 
 
@@ -121,7 +123,8 @@ g_configs.local =  {
   bcrypt_work_factor: g_configs.production.bcrypt_work_factor,
   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
+  min_time_between_emails_ms: g_configs.production.min_time_between_emails_ms,
+  max_compute_processes: undefined
 };
 
 // test environments are variations on local
@@ -204,6 +207,12 @@ if (process.env['BCRYPT_WORK_FACTOR']) {
   g_config.bcrypt_work_factor = parseInt(process.env['BCRYPT_WORK_FACTOR']);
 }
 
+// allow the number of cores used to be specified from the environment,
+// default will something reasonable.
+if (process.env['MAX_COMPUTE_PROCESSES']) {
+  g_config.max_compute_processes = parseInt(process.env['MAX_COMPUTE_PROCESSES']);
+}
+
 // what host/port shall we bind to?
 g_config.bind_to = {
   host: process.env['IP_ADDRESS'] || process.env['HOST'] || "127.0.0.1",
diff --git a/lib/keysigner/subprocess.js b/lib/keysigner/subprocess.js
new file mode 100644
index 000000000..3f0f108a1
--- /dev/null
+++ b/lib/keysigner/subprocess.js
@@ -0,0 +1,19 @@
+const
+config = require('../configuration'),
+ca = require('./ca.js');
+
+process.on('message', function(m) {
+  try {
+    // parse the pubkey
+    var pk = ca.parsePublicKey(m.pubkey);
+    
+    // same account, we certify the key
+    // we certify it for a day for now
+    var expiration = new Date();
+    expiration.setTime(new Date().valueOf() + config.get('certificate_validity_ms'));
+    var cert = ca.certify(m.email, pk, expiration);
+    process.send({"success": cert});
+  } catch(e) {
+    process.send({"error": e ? e.toString() : "unknown"});
+  }
+});
diff --git a/package.json b/package.json
index 0e2018f3d..c4f284eb9 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
     , "node-statsd": "https://github.com/mojodna/node-statsd/tarball/2584c08fad"
     , "connect-logger-statsd": "0.0.1"
     , "semver": "1.0.12"
+    , "compute-cluster": "0.0.2"
   }
   , "scripts": {
     "postinstall": "./scripts/generate_ephemeral_keys.sh",
-- 
GitLab