mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
mongo_runner: update code style and fiberize
This commit is contained in:
@@ -12,8 +12,7 @@ var project = require('./project.js');
|
||||
var warehouse = require('./warehouse.js');
|
||||
var auth = require('./auth.js');
|
||||
var config = require('./config.js');
|
||||
var release= require('./release.js');
|
||||
var Future = require('fibers/future');
|
||||
var release = require('./release.js');
|
||||
|
||||
// Given a site name passed on the command line (eg, 'mysite'), return
|
||||
// a fully-qualified hostname ('mysite.meteor.com').
|
||||
@@ -44,14 +43,11 @@ var hostedWithGalaxy = function (site) {
|
||||
// If the app is running (that is, by another 'meteor' process),
|
||||
// return the port where mongo is listening. If the app is not
|
||||
// running, return falsey.
|
||||
//
|
||||
// If called from a run that isn't in an app directory, print an error
|
||||
// and kill the process!
|
||||
var findMongoPort = function (appDir) {
|
||||
var fut = new Future;
|
||||
|
||||
var mongo_runner = require(path.join(__dirname, 'mongo_runner.js'));
|
||||
mongo_runner.find_mongo_port(appDir, function (port) {
|
||||
mongo_runner.findMongoPort(appDir, function (port) {
|
||||
fut.return(port);
|
||||
});
|
||||
|
||||
@@ -196,10 +192,12 @@ main.registerCommand({
|
||||
// (In particular, it's not sufficient to create the new app with
|
||||
// this version of the tools, and then stamp on the correct release
|
||||
// at the end.)
|
||||
var desiredRelease = release.forced ? release.current.name :
|
||||
release.latestDownloaded();
|
||||
if (release.current.name !== desiredRelease)
|
||||
throw new main.SpringboardToRelease(desiredRelease); // does not return
|
||||
if (! release.current.isCheckout()) {
|
||||
var desiredRelease = release.forced ? release.current.name :
|
||||
release.latestDownloaded();
|
||||
if (release.current.name !== desiredRelease)
|
||||
throw new main.SpringboardToRelease(desiredRelease); // does not return
|
||||
}
|
||||
|
||||
var appPath;
|
||||
if (options.args.length === 1)
|
||||
|
||||
@@ -2,279 +2,270 @@ var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var files = require('./files.js');
|
||||
var utils = require('./utils.js');
|
||||
|
||||
var _ = require('underscore');
|
||||
var unipackage = require('./unipackage.js');
|
||||
var Fiber = require('fibers');
|
||||
var Future = require('fibers/future');
|
||||
|
||||
// Find all running Mongo processes that were started by this program
|
||||
// (even by other simultaneous runs of this program). If passed,
|
||||
// appDir and port act as filters on the list of running mongos.
|
||||
//
|
||||
// Yields. Returns an object with keys pid, port, appDir.
|
||||
var findMongoPids = function (appDir, port, callback) {
|
||||
var fut = new Future;
|
||||
|
||||
/** 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 child_process = require('child_process');
|
||||
child_process.exec('ps ax',
|
||||
child_process.exec(
|
||||
'ps ax',
|
||||
function (error, stdout, stderr) {
|
||||
if (error) {
|
||||
callback({reason: error});
|
||||
} else {
|
||||
var pids = [];
|
||||
|
||||
_.each(stdout.split('\n'), function (ps_line) {
|
||||
// matches mongos we start.
|
||||
var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+)(?:\/|\\)\.meteor(?:\/|\\)local(?:\/|\\)db(?: |$)/);
|
||||
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);
|
||||
fut['throw'](new Error("Couldn't run ps ax: " + JSON.stringify(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
var ret = [];
|
||||
_.each(stdout.split('\n'), function (line) {
|
||||
// matches mongos we start.
|
||||
var m = line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+)(?:\/|\\)\.meteor(?:\/|\\)local(?:\/|\\)db(?: |$)/);
|
||||
if (m && m.length === 4) {
|
||||
var foundPid = parseInt(m[1]);
|
||||
var foundPort = parseInt(m[2]);
|
||||
var foundPath = m[3];
|
||||
|
||||
if ( (! port || port === foundPort) &&
|
||||
(! appDir || appDir === foundPath)) {
|
||||
ret.push({
|
||||
pid: foundPid,
|
||||
port: foundPort,
|
||||
appDir: foundPath
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fut['return'](ret);
|
||||
});
|
||||
|
||||
return fut.wait();
|
||||
};
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
// See if mongo is running already. Yields. Returns the port that
|
||||
// mongo is running on or null if mongo is not running.
|
||||
exports.findMongoPort = function (appDir, callback) {
|
||||
var pids = findMongoPids(appDir);
|
||||
|
||||
var pid = pids[0].pid;
|
||||
try {
|
||||
process.kill(pid, 0); // make sure it is still alive
|
||||
} catch (e) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
if (pids.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
callback(pids[0].port);
|
||||
});
|
||||
}
|
||||
var pid = pids[0].pid;
|
||||
try {
|
||||
process.kill(pid, 0); // make sure it is still alive
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return pid;
|
||||
};
|
||||
|
||||
|
||||
// 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).
|
||||
// Kill any other mongos running on our port. Yields, and returns once
|
||||
// they are all dead. Throws an exception on failure.
|
||||
//
|
||||
// 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) {
|
||||
find_mongo_pids(null, port, function (err, pids) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
var pids = findMongoPids(null, port);
|
||||
|
||||
if (! pids.length)
|
||||
return; // nothing to kill
|
||||
|
||||
// Go through the list serially. There really should only ever be
|
||||
// one but we're not taking any chances.
|
||||
_.each(pids, function (processInfo) {
|
||||
var pid = processInfo.pid;
|
||||
|
||||
// Send kill attempts and wait. First a SIGINT, then if it isn't
|
||||
// dead within 2 sec, SIGKILL. Check every 100ms to see if it's
|
||||
// dead.
|
||||
for (var attempts = 1; attempts <= 40; attempts ++) {
|
||||
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. on to the next one
|
||||
return;
|
||||
}
|
||||
|
||||
utils.sleep(100);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 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();
|
||||
|
||||
} else {
|
||||
// nothing to kill, fire OK callback
|
||||
callback();
|
||||
}
|
||||
// give up after 4 seconds.
|
||||
// XXX should actually catch this higher up and print a nice
|
||||
// error. foreseeable conditions should never result in exceptions
|
||||
// for the user.
|
||||
throw new Error("Can't kill running mongo (pid " + pid + ").");
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.launchMongo = function (options) {
|
||||
var handle = {stop: function (callback) { callback(); } };
|
||||
var onListen = options.onListen || function () {};
|
||||
var onExit = options.onExit || function () {};
|
||||
|
||||
// If we are passed an external mongo, assume it is launched and never
|
||||
// exits. Matches code in run.js:exports.run.
|
||||
|
||||
// Since it is externally managed, asking it to actually stop would be
|
||||
// impolite, so our stoppable handle is a noop
|
||||
if (process.env.MONGO_URL) {
|
||||
onListen();
|
||||
return handle;
|
||||
return {
|
||||
// Since it is externally managed, asking it to actually stop
|
||||
// would be impolite, so do nothing
|
||||
stop: function (callback) { callback(); }
|
||||
};
|
||||
}
|
||||
|
||||
var mongod_path = path.join(files.getDevBundle(),
|
||||
'mongodb',
|
||||
'bin',
|
||||
'mongod');
|
||||
var mongod_path = path.join(
|
||||
files.getDevBundle(), 'mongodb', 'bin', 'mongod');
|
||||
|
||||
// store data in app_dir
|
||||
// store data in appDir
|
||||
var dbPath = path.join(options.appDir, '.meteor', 'local', 'db');
|
||||
files.mkdir_p(dbPath, 0755);
|
||||
// add .gitignore if needed.
|
||||
files.addToGitignore(path.join(options.appDir, '.meteor'), 'local');
|
||||
|
||||
find_mongo_and_kill_it_dead(options.port, function (err) {
|
||||
Fiber(function (){
|
||||
if (err) {
|
||||
// XXX this was being passed to onListen and ignored before. should do
|
||||
// something better.
|
||||
throw {reason: "Can't kill running mongo: " + err.reason};
|
||||
}
|
||||
// There is a race here -- we want to return a handle immediately,
|
||||
// but we're not actually ready to service calls to handle.stop()
|
||||
// until Mongo has actually started up. For now, resolve that by
|
||||
// ignoring the call to stop.
|
||||
var handle = {
|
||||
stop: function (callback) { callback(); }
|
||||
};
|
||||
|
||||
var portFile = path.join(dbPath, 'METEOR-PORT');
|
||||
var createReplSet = true;
|
||||
Fiber(function () {
|
||||
find_mongo_and_kill_it_dead(options.port);
|
||||
|
||||
var portFile = path.join(dbPath, 'METEOR-PORT');
|
||||
var createReplSet = true;
|
||||
try {
|
||||
createReplSet = +(fs.readFileSync(portFile)) !== options.port;
|
||||
} catch (e) {
|
||||
if (!e || e.code !== 'ENOENT')
|
||||
throw e;
|
||||
}
|
||||
|
||||
// If this is the first time we're using this DB, or we changed
|
||||
// port since the last time, then we want to destroying any
|
||||
// existing replSet configuration and create a new one. First we
|
||||
// delete the "local" database if it exists. (It's a pain and slow
|
||||
// to change the port in an existing replSet configuration. It's
|
||||
// also a little slow to initiate a new replSet, thus the attempt
|
||||
// to not do it unless the port changes.)
|
||||
if (createReplSet) {
|
||||
try {
|
||||
createReplSet = +(fs.readFileSync(portFile)) !== options.port;
|
||||
var dbFiles = fs.readdirSync(dbPath);
|
||||
} catch (e) {
|
||||
if (!e || e.code !== 'ENOENT')
|
||||
throw e;
|
||||
}
|
||||
|
||||
// If this is the first time we're using this DB, or we changed
|
||||
// port since the last time, then we want to destroying any
|
||||
// existing replSet configuration and create a new one. First we
|
||||
// delete the "local" database if it exists. (It's a pain and
|
||||
// slow to change the port in an existing replSet
|
||||
// configuration. It's also a little slow to initiate a new
|
||||
// replSet, thus the attempt to not do it unless the port
|
||||
// changes.)
|
||||
if (createReplSet) {
|
||||
try {
|
||||
var dbFiles = fs.readdirSync(dbPath);
|
||||
} catch (e) {
|
||||
if (!e || e.code !== 'ENOENT')
|
||||
throw e;
|
||||
}
|
||||
_.each(dbFiles, function (dbFile) {
|
||||
if (/^local\./.test(dbFile))
|
||||
fs.unlinkSync(path.join(dbPath, dbFile));
|
||||
});
|
||||
|
||||
// Load mongo-livedata so we'll be able to talk to it.
|
||||
var mongoNpmModule = unipackage.load({
|
||||
library: release.current.library,
|
||||
packages: [ 'mongo-livedata' ],
|
||||
release: release.current.name,
|
||||
})['mongo-livedata'].MongoInternals.NpmModule;
|
||||
}
|
||||
|
||||
// Start mongod with a dummy replSet and wait for it to listen.
|
||||
var child_process = require('child_process');
|
||||
var replSetName = 'meteor';
|
||||
var proc = child_process.spawn(mongod_path, [
|
||||
// nb: cli-test.sh and find_mongo_pids make strong assumptions about the
|
||||
// order of the arguments! Check them before changing any arguments.
|
||||
'--bind_ip', '127.0.0.1',
|
||||
'--smallfiles',
|
||||
'--nohttpinterface',
|
||||
'--port', options.port,
|
||||
'--dbpath', dbPath,
|
||||
// Use an 8MB oplog rather than 256MB. Uses less space on disk and
|
||||
// initializes faster. (Not recommended for production!)
|
||||
'--oplogSize', '8',
|
||||
'--replSet', replSetName
|
||||
]);
|
||||
|
||||
var stderrOutput = '';
|
||||
proc.stderr.setEncoding('utf8');
|
||||
proc.stderr.on('data', function (data) {
|
||||
stderrOutput += data;
|
||||
_.each(dbFiles, function (dbFile) {
|
||||
if (/^local\./.test(dbFile))
|
||||
fs.unlinkSync(path.join(dbPath, dbFile));
|
||||
});
|
||||
|
||||
var callOnExit = function (code, signal) {
|
||||
onExit(code, signal, stderrOutput);
|
||||
};
|
||||
proc.on('exit', callOnExit);
|
||||
// Load mongo-livedata so we'll be able to talk to it.
|
||||
var mongoNpmModule = unipackage.load({
|
||||
library: release.current.library,
|
||||
packages: [ 'mongo-livedata' ],
|
||||
release: release.current.name,
|
||||
})['mongo-livedata'].MongoInternals.NpmModule;
|
||||
}
|
||||
|
||||
handle.stop = function (callback) {
|
||||
var tries = 0;
|
||||
var exited = false;
|
||||
proc.removeListener('exit', callOnExit);
|
||||
proc.kill('SIGINT');
|
||||
callback && callback(err);
|
||||
};
|
||||
// Start mongod with a dummy replSet and wait for it to listen.
|
||||
var child_process = require('child_process');
|
||||
var replSetName = 'meteor';
|
||||
var proc = child_process.spawn(mongod_path, [
|
||||
// nb: cli-test.sh and findMongoPids make strong assumptions about the
|
||||
// order of the arguments! Check them before changing any arguments.
|
||||
'--bind_ip', '127.0.0.1',
|
||||
'--smallfiles',
|
||||
'--nohttpinterface',
|
||||
'--port', options.port,
|
||||
'--dbpath', dbPath,
|
||||
// Use an 8MB oplog rather than 256MB. Uses less space on disk and
|
||||
// initializes faster. (Not recommended for production!)
|
||||
'--oplogSize', '8',
|
||||
'--replSet', replSetName
|
||||
]);
|
||||
|
||||
proc.stdout.setEncoding('utf8');
|
||||
var listening = false;
|
||||
var replSetReady = false;
|
||||
var maybeCallOnListen = function () {
|
||||
if (listening && replSetReady) {
|
||||
if (createReplSet)
|
||||
fs.writeFileSync(portFile, options.port);
|
||||
onListen();
|
||||
}
|
||||
};
|
||||
proc.stdout.on('data', function (data) {
|
||||
if (/ \[initandlisten\] waiting for connections on port/.test(data)) {
|
||||
if (createReplSet) {
|
||||
// Connect to it and start a replset.
|
||||
var db = new mongoNpmModule.Db(
|
||||
'meteor', new mongoNpmModule.Server('127.0.0.1', options.port),
|
||||
{safe: true});
|
||||
db.open(function (err, db) {
|
||||
var stderrOutput = '';
|
||||
proc.stderr.setEncoding('utf8');
|
||||
proc.stderr.on('data', function (data) {
|
||||
stderrOutput += data;
|
||||
});
|
||||
|
||||
var callOnExit = function (code, signal) {
|
||||
onExit(code, signal, stderrOutput);
|
||||
};
|
||||
proc.on('exit', callOnExit);
|
||||
|
||||
handle.stop = function (callback) {
|
||||
proc.removeListener('exit', callOnExit);
|
||||
proc.kill('SIGINT');
|
||||
callback && callback(err);
|
||||
};
|
||||
|
||||
proc.stdout.setEncoding('utf8');
|
||||
var listening = false;
|
||||
var replSetReady = false;
|
||||
var maybeCallOnListen = function () {
|
||||
if (listening && replSetReady) {
|
||||
if (createReplSet)
|
||||
fs.writeFileSync(portFile, options.port);
|
||||
onListen();
|
||||
}
|
||||
};
|
||||
proc.stdout.on('data', function (data) {
|
||||
if (/ \[initandlisten\] waiting for connections on port/.test(data)) {
|
||||
if (createReplSet) {
|
||||
// Connect to it and start a replset.
|
||||
var db = new mongoNpmModule.Db(
|
||||
'meteor', new mongoNpmModule.Server('127.0.0.1', options.port),
|
||||
{safe: true});
|
||||
db.open(function (err, db) {
|
||||
if (err)
|
||||
throw err;
|
||||
db.admin().command({
|
||||
replSetInitiate: {
|
||||
_id: replSetName,
|
||||
members: [{_id : 0, host: '127.0.0.1:' + options.port}]
|
||||
}
|
||||
}, function (err, result) {
|
||||
if (err)
|
||||
throw err;
|
||||
db.admin().command({
|
||||
replSetInitiate: {
|
||||
_id: replSetName,
|
||||
members: [{_id : 0, host: '127.0.0.1:' + options.port}]
|
||||
}
|
||||
}, function (err, result) {
|
||||
if (err)
|
||||
throw err;
|
||||
db.close(true);
|
||||
});
|
||||
db.close(true);
|
||||
});
|
||||
}
|
||||
listening = true;
|
||||
maybeCallOnListen();
|
||||
});
|
||||
}
|
||||
listening = true;
|
||||
maybeCallOnListen();
|
||||
}
|
||||
|
||||
if (/ \[rsMgr\] replSet PRIMARY/.test(data)) {
|
||||
replSetReady = true;
|
||||
maybeCallOnListen();
|
||||
}
|
||||
});
|
||||
}).run();
|
||||
|
||||
if (/ \[rsMgr\] replSet PRIMARY/.test(data)) {
|
||||
replSetReady = true;
|
||||
maybeCallOnListen();
|
||||
}
|
||||
});
|
||||
}).run();
|
||||
});
|
||||
return handle;
|
||||
};
|
||||
|
||||
@@ -115,6 +115,15 @@ exports.getAgentInfo = function () {
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Wait for 'ms' milliseconds, and then return. Yields. (Must be
|
||||
// called within a fiber, and blocks only the calling fiber, not the
|
||||
// whole program.)
|
||||
exports.sleep = function (ms) {
|
||||
var fut = new Future;
|
||||
setTimeout(function () { fut['return']() }, ms);
|
||||
fut.wait();
|
||||
};
|
||||
|
||||
// True if this looks like a valid email address. We deliberately
|
||||
// don't support
|
||||
// - quoted usernames (eg, "foo"@bar.com, " "@bar.com, "@"@bar.com)
|
||||
|
||||
Reference in New Issue
Block a user