From d216630d0edec8cf5c0734e2ab6f702fb9aa4cb3 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 9 Mar 2012 15:30:53 -0800 Subject: [PATCH 1/3] Refactor existing mongo running code into one file. --- app/lib/mongo_runner.js | 161 ++++++++++++++++++++++++++++++++++++++++ app/meteor/meteor.js | 17 +---- app/meteor/run.js | 140 ++-------------------------------- 3 files changed, 168 insertions(+), 150 deletions(-) create mode 100644 app/lib/mongo_runner.js diff --git a/app/lib/mongo_runner.js b/app/lib/mongo_runner.js new file mode 100644 index 0000000000..254018b246 --- /dev/null +++ b/app/lib/mongo_runner.js @@ -0,0 +1,161 @@ +var fs = require("fs"); +var path = require("path"); +var spawn = require('child_process').spawn; + +var files = require('../lib/files.js'); + +var _ = require('../lib/third/underscore.js'); + + +// See if mongo is running already. If so, return the current port. If +// not, return null. +exports.find_mongo_port = function (app_dir) { + var pid_path = path.join(app_dir, '.meteor/local/mongod.pid'); + var port_path = path.join(app_dir, '.meteor/local/mongod.port'); + var port; + + try { + var pid_data = parseInt(fs.readFileSync(pid_path)); + process.kill(pid_data, 0); // make sure it is still alive + port = parseInt(fs.readFileSync(port_path)); + } catch (e) { + return null; + } + + return port; +}; + + + +// Try to kill any other mongos running on our port. Calls callback +// once they are all gone. Callback takes one arg: err (falsy means all +// good). +// +// This is a big hammer for dealing with still running mongos, but +// smaller hammers have failed before and it is getting tiresome. +var find_mongo_and_kill_it_dead = function (port, callback) { + var proc = spawn('ps', ['ax']); + var data = ''; + proc.stdout.on('data', function (d) { + data += d; + }); + + proc.on('exit', function (code, signal) { + if (code === 0) { + var kill_pids = []; + + _.each(data.split('\n'), function (ps_line) { + // matches mongos we start + var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+\.meteor\/local\/db)\s*$/); + if (m && m.length === 4) { + var found_pid = m[1]; + var found_port = m[2]; + + if (port === parseInt(found_port)) { + kill_pids.push(found_pid); + } + } + }); + + + if (kill_pids.length) { + // Send kill attempts and wait. First a SIGINT, then if it isn't + // dead within 2 sec, SIGKILL. This goes through the list + // serially, but thats OK because there really should only ever be + // one. + var attempts = 0; + var dead_yet = function () { + attempts = attempts + 1; + var pid = kill_pids[0]; + var signal = 0; + if (attempts === 1) + signal = 'SIGINT'; + else if (attempts === 20 || attempts === 30) + signal = 'SIGKILL'; + try { + process.kill(pid, signal); + } catch (e) { + // it's dead. remove this pid from the list. + kill_pids.shift(); + + // if no more in the list, we're done! + if (!kill_pids.length) { + callback(); + return; + } + } + if (attempts === 40) { + // give up after 4 seconds. + callback({ + reason: "Can't kill running mongo (pid " + pid + ")."}); + return; + } + + // recurse + setTimeout(dead_yet, 100); + }; + dead_yet(); + + } else { + // nothing to kill, fire OK callback + callback(); + } + } else { + callback({reason: 'ps exit code ' + code}); + } + }); +}; + +exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callback) { + launch_callback = launch_callback || function () {}; + on_exit_callback = on_exit_callback || function () {}; + + // If we are passed an external mongo, assume it is launched and never + // exits. Matches code in run.js:exports.run. + if (process.env.MONGO_URL) { + launch_callback(); + return; + } + + var mongod_path = path.join(files.get_dev_bundle(), 'mongodb/bin/mongod'); + + // store data in app_dir + var data_path = path.join(app_dir, '.meteor/local/db'); + files.mkdir_p(data_path, 0755); + var port_path = path.join(app_dir, '.meteor/local/mongod.port'); + // add .gitignore if needed. + files.add_to_gitignore(path.join(app_dir, '.meteor'), 'local'); + + find_mongo_and_kill_it_dead(port, function (err) { + if (err) { + launch_callback({reason: "Can't kill running mongo: " + err.reason}); + return; + } + + var proc = spawn(mongod_path, [ + '--bind_ip', '127.0.0.1', '--port', port, + '--dbpath', data_path + ]); + + // write port file. + fs.writeFileSync(port_path, port.toString(), 'ascii'); + + proc.on('exit', function (code, signal) { + on_exit_callback(code, signal); + }); + + // proc.stderr.setEncoding('utf8'); + // proc.stderr.on('data', function (data) { + // process.stdout.write(data); + // }); + + proc.stdout.setEncoding('utf8'); + proc.stdout.on('data', function (data) { + // process.stdout.write(data); + if (/ \[initandlisten\] waiting for connections on port/.test(data)) + launch_callback(); + }); + }); + +}; + diff --git a/app/meteor/meteor.js b/app/meteor/meteor.js index 9d547151da..12d7a66e0d 100644 --- a/app/meteor/meteor.js +++ b/app/meteor/meteor.js @@ -60,21 +60,8 @@ cmd + ": You're not in a Meteor project directory.\n" + // not, return null. var find_mongo_port = function (cmd) { var app_dir = require_project(cmd); - - var fs = require("fs"); - var pid_path = path.join(app_dir, '.meteor/local/mongod.pid'); - var port_path = path.join(app_dir, '.meteor/local/mongod.port'); - var port; - - try { - var pid_data = parseInt(fs.readFileSync(pid_path)); - process.kill(pid_data, 0); // make sure it is still alive - port = parseInt(fs.readFileSync(port_path)); - } catch (e) { - return null; - } - - return port; + var mongo_runner = require('../lib/mongo_runner.js'); + return mongo_runner.find_mongo_port(app_dir); }; Commands = []; diff --git a/app/meteor/run.js b/app/meteor/run.js index 2efec34bc9..8c18651586 100644 --- a/app/meteor/run.js +++ b/app/meteor/run.js @@ -9,6 +9,7 @@ var httpProxy = require('http-proxy'); var files = require('../lib/files.js'); var updater = require('../lib/updater.js'); var bundler = require('../lib/bundler.js'); +var mongo_runner = require('../lib/mongo_runner.js'); var _ = require('../lib/third/underscore.js'); @@ -125,139 +126,6 @@ var start_proxy = function (outer_port, inner_port, callback) { ////////// MongoDB ////////// -// Try to kill any other mongos running on our port. Calls callback -// once they are all gone. Callback takes one arg: err (falsy means all -// good). -// -// This is a big hammer for dealing with still running mongos, but -// smaller hammers have failed before and it is getting tiresome. -var find_mongo_and_kill_it_dead = function (data_path, port, callback) { - var proc = spawn('ps', ['ax']); - var data = ''; - proc.stdout.on('data', function (d) { - data += d; - }); - - proc.on('exit', function (code, signal) { - if (code === 0) { - var kill_pids = []; - - _.each(data.split('\n'), function (ps_line) { - // matches mongos we start - var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+\.meteor\/local\/db)\s*$/); - if (m && m.length === 4) { - var found_pid = m[1]; - var found_port = m[2]; - - if (port === parseInt(found_port)) { - kill_pids.push(found_pid); - } - } - }); - - - if (kill_pids.length) { - // Send kill attempts and wait. First a SIGINT, then if it isn't - // dead within 2 sec, SIGKILL. This goes through the list - // serially, but thats OK because there really should only ever be - // one. - var attempts = 0; - var dead_yet = function () { - attempts = attempts + 1; - var pid = kill_pids[0]; - var signal = 0; - if (attempts === 1) - signal = 'SIGINT'; - else if (attempts === 20 || attempts === 30) - signal = 'SIGKILL'; - try { - process.kill(pid, signal); - } catch (e) { - // it's dead. remove this pid from the list. - kill_pids.shift(); - - // if no more in the list, we're done! - if (!kill_pids.length) { - callback(); - return; - } - } - if (attempts === 40) { - // give up after 4 seconds. - callback({ - reason: "Can't kill running mongo (pid " + pid + ")."}); - return; - } - - // recurse - setTimeout(dead_yet, 100); - }; - dead_yet(); - - } else { - // nothing to kill, fire OK callback - callback(); - } - } else { - callback({reason: 'ps exit code ' + code}); - } - }); -}; - -var launch_mongo = function (app_dir, port, launch_callback, on_exit_callback) { - launch_callback = launch_callback || function () {}; - on_exit_callback = on_exit_callback || function () {}; - - // If we are passed an external mongo, assume it is launched and never - // exits. Matches code in exports.run. - if (process.env.MONGO_URL) { - launch_callback(); - return; - } - - var mongod_path = path.join(files.get_dev_bundle(), 'mongodb/bin/mongod'); - - // store data in app_dir - var data_path = path.join(app_dir, '.meteor/local/db'); - files.mkdir_p(data_path, 0755); - var port_path = path.join(app_dir, '.meteor/local/mongod.port'); - // add .gitignore if needed. - files.add_to_gitignore(path.join(app_dir, '.meteor'), 'local'); - - find_mongo_and_kill_it_dead(data_path, port, function (err) { - if (err) { - console.log("Can't kill running mongo:", err.reason); - process.exit(1); - } - - var proc = spawn(mongod_path, [ - '--bind_ip', '127.0.0.1', '--port', port, - '--dbpath', data_path - ]); - - // write port file. - fs.writeFileSync(port_path, port.toString(), 'ascii'); - - proc.on('exit', function (code, signal) { - console.log("Unexpected mongo exit code " + code + ". Restarting."); - on_exit_callback(); - }); - - // proc.stderr.setEncoding('utf8'); - // proc.stderr.on('data', function (data) { - // process.stdout.write(data); - // }); - - proc.stdout.setEncoding('utf8'); - proc.stdout.on('data', function (data) { - // process.stdout.write(data); - if (/ \[initandlisten\] waiting for connections on port/.test(data)) - launch_callback(); - }); - }); - -}; - var log_to_clients = function (msg) { server_log.push(msg); if (server_log.length > 100) { @@ -678,13 +546,15 @@ exports.run = function (app_dir, bundle_opts, port) { var mongo_err_count = 0; var mongo_err_timer; var launch = function () { - launch_mongo( + mongo_runner.launch_mongo( app_dir, mongo_port, function () { // On Mongo startup complete restart_server(); }, - function () { // On Mongo dead + function (code, signal) { // On Mongo dead + console.log("Unexpected mongo exit code " + code + ". Restarting."); + // if mongo dies 3 times with less than 5 seconds between each, // declare it failed and die. mongo_err_count += 1; From 86ed80d5811db37362373da70166c3186f1a5980 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 9 Mar 2012 16:55:21 -0800 Subject: [PATCH 2/3] Rework mongo runner to find port from ps list instead of from file. --- app/lib/mongo_runner.js | 178 ++++++++++++++++++++++------------------ app/meteor/meteor.js | 44 +++++----- 2 files changed, 121 insertions(+), 101 deletions(-) diff --git a/app/lib/mongo_runner.js b/app/lib/mongo_runner.js index 254018b246..aa25d8d907 100644 --- a/app/lib/mongo_runner.js +++ b/app/lib/mongo_runner.js @@ -7,25 +7,70 @@ var files = require('../lib/files.js'); var _ = require('../lib/third/underscore.js'); -// See if mongo is running already. If so, return the current port. If -// not, return null. -exports.find_mongo_port = function (app_dir) { - var pid_path = path.join(app_dir, '.meteor/local/mongod.pid'); - var port_path = path.join(app_dir, '.meteor/local/mongod.port'); - var port; +/** Internal. + * + * If passed, app_dir and port act as filters on the list of running mongos. + * + * callback is called with (err, [{pid, port, app_dir}]) + */ +var find_mongo_pids = function (app_dir, port, callback) { + // 'ps ax' should be standard across all MacOS and Linux. + var proc = spawn('ps', ['ax']); + var data = ''; + proc.stdout.on('data', function (d) { + data += d; + }); - try { - var pid_data = parseInt(fs.readFileSync(pid_path)); - process.kill(pid_data, 0); // make sure it is still alive - port = parseInt(fs.readFileSync(port_path)); - } catch (e) { - return null; - } + proc.on('exit', function (code, signal) { + if (code === 0) { + var pids = []; - return port; + _.each(data.split('\n'), function (ps_line) { + // matches mongos we start. + var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+)\/\.meteor\/local\/db\s*$/); + if (m && m.length === 4) { + var found_pid = parseInt(m[1]); + var found_port = parseInt(m[2]); + var found_path = m[3]; + + if ( (!port || port === found_port) && + (!app_dir || app_dir === found_path)) { + pids.push({ + pid: found_pid, port: found_port, app_dir: found_path}); + } + } + }); + + callback(null, pids); + } else { + callback({reason: 'ps exit code ' + code}); + } + }); }; +// See if mongo is running already. Callback takes a single argument, +// 'port', which is the port mongo is running on or null if mongo is not +// running. +exports.find_mongo_port = function (app_dir, callback) { + find_mongo_pids(app_dir, null, function (err, pids) { + if (err || pids.length !== 1) { + callback(null); + return; + } + + var pid = pids[0].pid; + try { + process.kill(pid, 0); // make sure it is still alive + } catch (e) { + callback(null); + return; + } + + callback(pids[0].port); + }); +} + // Try to kill any other mongos running on our port. Calls callback // once they are all gone. Callback takes one arg: err (falsy means all @@ -34,74 +79,53 @@ exports.find_mongo_port = function (app_dir) { // This is a big hammer for dealing with still running mongos, but // smaller hammers have failed before and it is getting tiresome. var find_mongo_and_kill_it_dead = function (port, callback) { - var proc = spawn('ps', ['ax']); - var data = ''; - proc.stdout.on('data', function (d) { - data += d; - }); + find_mongo_pids(null, port, function (err, pids) { + if (err) { + callback(err); + return; + } - proc.on('exit', function (code, signal) { - if (code === 0) { - var kill_pids = []; + if (pids.length) { + // Send kill attempts and wait. First a SIGINT, then if it isn't + // dead within 2 sec, SIGKILL. This goes through the list + // serially, but thats OK because there really should only ever be + // one. + var attempts = 0; + var dead_yet = function () { + attempts = attempts + 1; + var pid = pids[0].pid; + var signal = 0; + if (attempts === 1) + signal = 'SIGINT'; + else if (attempts === 20 || attempts === 30) + signal = 'SIGKILL'; + try { + process.kill(pid, signal); + } catch (e) { + // it's dead. remove this pid from the list. + pids.shift(); - _.each(data.split('\n'), function (ps_line) { - // matches mongos we start - var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+\.meteor\/local\/db)\s*$/); - if (m && m.length === 4) { - var found_pid = m[1]; - var found_port = m[2]; - - if (port === parseInt(found_port)) { - kill_pids.push(found_pid); - } - } - }); - - - if (kill_pids.length) { - // Send kill attempts and wait. First a SIGINT, then if it isn't - // dead within 2 sec, SIGKILL. This goes through the list - // serially, but thats OK because there really should only ever be - // one. - var attempts = 0; - var dead_yet = function () { - attempts = attempts + 1; - var pid = kill_pids[0]; - var signal = 0; - if (attempts === 1) - signal = 'SIGINT'; - else if (attempts === 20 || attempts === 30) - signal = 'SIGKILL'; - try { - process.kill(pid, signal); - } catch (e) { - // it's dead. remove this pid from the list. - kill_pids.shift(); - - // if no more in the list, we're done! - if (!kill_pids.length) { - callback(); - return; - } - } - if (attempts === 40) { - // give up after 4 seconds. - callback({ - reason: "Can't kill running mongo (pid " + pid + ")."}); + // if no more in the list, we're done! + if (!pids.length) { + callback(); return; } + } + if (attempts === 40) { + // give up after 4 seconds. + callback({ + reason: "Can't kill running mongo (pid " + pid + ")."}); + return; + } - // recurse - setTimeout(dead_yet, 100); - }; - dead_yet(); + // recurse + setTimeout(dead_yet, 100); + }; + dead_yet(); - } else { - // nothing to kill, fire OK callback - callback(); - } } else { - callback({reason: 'ps exit code ' + code}); + // nothing to kill, fire OK callback + callback(); } }); }; @@ -122,7 +146,6 @@ exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callbac // store data in app_dir var data_path = path.join(app_dir, '.meteor/local/db'); files.mkdir_p(data_path, 0755); - var port_path = path.join(app_dir, '.meteor/local/mongod.port'); // add .gitignore if needed. files.add_to_gitignore(path.join(app_dir, '.meteor'), 'local'); @@ -137,9 +160,6 @@ exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callbac '--dbpath', data_path ]); - // write port file. - fs.writeFileSync(port_path, port.toString(), 'ascii'); - proc.on('exit', function (code, signal) { on_exit_callback(code, signal); }); diff --git a/app/meteor/meteor.js b/app/meteor/meteor.js index 12d7a66e0d..2da469c2f8 100644 --- a/app/meteor/meteor.js +++ b/app/meteor/meteor.js @@ -56,12 +56,10 @@ cmd + ": You're not in a Meteor project directory.\n" + process.exit(1); }; -// See if mongo is running already. If so, return the current port. If -// not, return null. -var find_mongo_port = function (cmd) { +var find_mongo_port = function (cmd, callback) { var app_dir = require_project(cmd); var mongo_runner = require('../lib/mongo_runner.js'); - return mongo_runner.find_mongo_port(app_dir); + mongo_runner.find_mongo_port(app_dir, callback); }; Commands = []; @@ -388,22 +386,23 @@ Commands.push({ if (new_argv._.length === 1) { // localhost mode - var mongod_port = find_mongo_port("mongo"); - if (!mongod_port) { - process.stdout.write( + find_mongo_port("mongo", function (mongod_port) { + if (!mongod_port) { + process.stdout.write( "mongo: Meteor isn't running.\n" + "\n" + "This command only works while Meteor is running your application\n" + "locally. Start your application first.\n"); - process.exit(1); - } + process.exit(1); + } - var mongo_url = "mongodb://127.0.0.1:" + mongod_port + "/meteor"; + var mongo_url = "mongodb://127.0.0.1:" + mongod_port + "/meteor"; - if (new_argv.url) - console.log(mongo_url) - else - deploy.run_mongo_shell(mongo_url); + if (new_argv.url) + console.log(mongo_url) + else + deploy.run_mongo_shell(mongo_url); + }); } else if (new_argv._.length === 2) { // remote mode @@ -504,20 +503,21 @@ Commands.push({ var app_dir = path.resolve(require_project("reset")); - var mongod_port = find_mongo_port("reset"); - if (mongod_port) { - process.stdout.write( + find_mongo_port("reset", function (mongod_port) { + if (mongod_port) { + process.stdout.write( "reset: Meteor is running.\n" + "\n" + "This command does not work while Meteor is running your application.\n" + "Exit the running meteor development server.\n"); - process.exit(1); - } + process.exit(1); + } - var local_dir = path.join(app_dir, '.meteor/local'); - files.rm_recursive(local_dir); + var local_dir = path.join(app_dir, '.meteor/local'); + files.rm_recursive(local_dir); - process.stdout.write("Project reset.\n"); + process.stdout.write("Project reset.\n"); + }); } }); From c537b9de0302a431b8709579954f16878c8f920e Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Fri, 9 Mar 2012 18:43:38 -0800 Subject: [PATCH 3/3] Add (crappy) tests for running meteor and mongo. --- tests/cli/test.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/cli/test.sh b/tests/cli/test.sh index 4088496916..b6d239bc44 100755 --- a/tests/cli/test.sh +++ b/tests/cli/test.sh @@ -65,6 +65,55 @@ $METEOR bundle foo.tar.gz test -f foo.tar.gz +echo "... run" + +# kill any old test meteor +# there is probably a better way to do this, but it is at least portable across macos and linux +ps ax | grep -e 'meteor.js -p 9100' | grep -v grep | awk '{print $1}' | xargs kill + +! $METEOR mongo > /dev/null 2>&1 +$METEOR reset > /dev/null 2>&1 + +test ! -d .meteor/local +! ps ax | grep -e '--bind_ip 127.0.0.1 --port 9102' | grep -v grep > /dev/null + +PORT=9100 +$METEOR -p $PORT > /dev/null 2>&1 & +METEOR_PID=$! + +sleep 1 # XXX XXX lame + +test -d .meteor/local/db +ps ax | grep -e '--bind_ip 127.0.0.1 --port 9102' | grep -v grep > /dev/null +curl -s "http://localhost:$PORT" > /dev/null + +echo "show collections" | $METEOR mongo + +# kill meteor, see mongo is still running +kill $METEOR_PID + +sleep 4 # XXX XXX lame. have to wait for inner app to die via keepalive! + +! ps ax | grep "$METEOR_PID" | grep -v grep > /dev/null +ps ax | grep -e '--bind_ip 127.0.0.1 --port 9102' | grep -v grep > /dev/null + + +echo "... rerun" + +$METEOR -p $PORT > /dev/null 2>&1 & +METEOR_PID=$! + +sleep 1 # XXX XXX lame + +ps ax | grep -e '--bind_ip 127.0.0.1 --port 9102' | grep -v grep > /dev/null +curl -s "http://localhost:$PORT" > /dev/null + +kill $METEOR_PID +ps ax | grep -e '--bind_ip 127.0.0.1 --port 9102' | grep -v grep | awk '{print $1}' | xargs kill + + + + # XXX more tests here!