Merge pull request +10204 from c9/tmuxspawn

Fix tmux getting into restart loop when profile prints something
pull/223/head
Ruben Daniels 2015-11-04 09:02:38 -08:00
commit 584f9ea55b
6 zmienionych plików z 132 dodań i 159 usunięć

221
node_modules/vfs-local/localfs.js wygenerowano vendored
Wyświetl plik

@ -1449,16 +1449,7 @@ module.exports = function setup(fsOptions) {
function spawn(executablePath, options, callback) { function spawn(executablePath, options, callback) {
var args = options.args || []; var args = options.args || [];
if (options.hasOwnProperty('env')) { _setDefaultEnv(options);
options.env.__proto__ = fsOptions.defaultEnv;
} else {
options.env = fsOptions.defaultEnv;
}
if (options.cwd && options.cwd.charAt(0) == "~")
options.cwd = options.env.HOME + options.cwd.substr(1);
if (transformPath && options.cwd)
options.cwd = transformPath(options.cwd);
resolvePath(executablePath, { resolvePath(executablePath, {
nocheck : 1, nocheck : 1,
@ -1495,25 +1486,8 @@ module.exports = function setup(fsOptions) {
var args = options.args || []; var args = options.args || [];
delete options.args; delete options.args;
if (options.hasOwnProperty('env')) { _setDefaultEnv(options);
options.env.__proto__ = fsOptions.defaultEnv; delete options.env.TMUX;
} else {
options.env = fsOptions.defaultEnv;
}
// Pty is only reading from the object itself;
var env = {};
for (var prop in options.env) {
if (prop == "TMUX") continue;
env[prop] = options.env[prop];
}
options.env = env;
if (options.cwd && options.cwd.charAt(0) == "~")
options.cwd = env.HOME + options.cwd.substr(1);
if (transformPath && options.cwd)
options.cwd = transformPath(options.cwd);
if (options.testing) { if (options.testing) {
args.forEach(function(arg, i){ args.forEach(function(arg, i){
@ -1605,36 +1579,29 @@ module.exports = function setup(fsOptions) {
* @param {Boolean} [options.base] The base path to store the watch files * @param {Boolean} [options.base] The base path to store the watch files
*/ */
function tmuxspawn(ignored, options, callback) { function tmuxspawn(ignored, options, callback) {
var watchFile = join(options.base || "", ".run_" + options.session + ".watch");
if (watchFile.charAt(0) == "~")
watchFile = join(process.env.HOME, watchFile.substr(1));
var tmuxName = options.tmuxName || TMUXNAME; var tmuxName = options.tmuxName || TMUXNAME;
var session = options.session;
function fetchPid(callback, retries){ function fetchPid(callback, retries){
if (!retries) retries = 0; if (!retries) retries = 0;
// use axww to get full command _execFile(TMUX, [
_execFile("ps", ["ax", "-ww", "-opid,command"], { "-u2", "-L", tmuxName, "-C",
"list-panes", "-F", "c9-pid-#{pane_pid}-#{pane_dead}-#{pane_status}",
"-t", session
], {
maxBuffer: 1000 * 1024 maxBuffer: 1000 * 1024
}, function(err, stdout){ }, function(err, stdout){
var matches = (stdout || "").split("\n").filter(function(line) { var matches = /c9-pid-(\d+)-(\d)-/.exec(stdout);
return line.indexOf(watchFile) > -1 var isDead = parseInt(matches && matches[2], 10);
&& !/\-L cloud9[\d\.]+ new/.test(line) var pid = isDead ? 0 : parseInt(matches && matches[1], 10);
&& line.indexOf("2> /dev/null") === -1;
});
// logToFile(options.session + ":" + JSON.stringify(matches)); if (!pid && !isDead && retries < 10) {
if (!matches.length && retries < 10) {
// logToFile(options.session + ": RETRY MATCH");
setTimeout(fetchPid.bind(null, callback, ++retries), 30); setTimeout(fetchPid.bind(null, callback, ++retries), 30);
return; return;
} }
callback(err, { callback(err, {
pid: matches && matches.length pid: pid || -1
? parseInt(matches[0].trim(), 10)
: -1
}); });
}); });
} }
@ -1770,7 +1737,7 @@ module.exports = function setup(fsOptions) {
// logToFile("Kill: " + options.session); // logToFile("Kill: " + options.session);
_execFile(TMUX, _execFile(TMUX,
["-L", tmuxName, "kill-session", "-t", options.session], ["-L", tmuxName, "-C", "kill-session", "-t", options.session],
function(err){ function(err){
if (!options.command) if (!options.command)
return callback(err, {}); return callback(err, {});
@ -1828,9 +1795,6 @@ module.exports = function setup(fsOptions) {
args = ["-u2", "-L", tmuxName, "new", "-s", options.session]; args = ["-u2", "-L", tmuxName, "new", "-s", options.session];
if (options.idle)
options.command = "echo '[Idle]'";
if (options.terminal) { if (options.terminal) {
args.push("export ISOUTPUTPANE=0;" args.push("export ISOUTPUTPANE=0;"
+ (options.defaultEditor + (options.defaultEditor
@ -1838,41 +1802,46 @@ module.exports = function setup(fsOptions) {
: "") : "")
+ BASH + " -l"); + BASH + " -l");
} }
else if (options.idle) {
else if (options.command) args.push(BASH + " -l -c 'printf \"\\e[01;34m[Idle]\\e[0m\\n\""
args.push((BASH + " -l -c '" + "; sleep 0.1;'");
+ options.command.replace(/'/g, "'\\''") + "'") }
+ "; ([ -e '" + watchFile + "' ] && " else if (options.command) {
+ "rm '" + watchFile + "')"); args.push(BASH + " -l -c '"
+ options.command.replace(/'/g, "'\\''")
+ '; printf "\\e[01;30m\\n\\nProcess exited with code: $?\\e[0m\\n"'
+ "; sleep 0.1;'");
}
args.push( args.push(
";", "set-option", "-g", "status", "off", ";", "set", "-q", "-g", "status", "off",
";", "set-option", "destroy-unattached", "off", ";", "set", "-q", "destroy-unattached", "off",
";", "set-option", "mouse-select-pane", "on", ";", "set", "-q", "mouse-select-pane", "on",
";", "set-option", "set-titles", "on", ";", "set", "-q", "set-titles", "on",
";", "set-option", "quiet", "off", ";", "set", "-q", "quiet", "on",
";", "set-option", "-g", "prefix", "C-b", ";", "set", "-q", "-g", "prefix", "C-b",
";", "set-option", "-g", "terminal-overrides", "'xterm:colors=256'" ";", "set", "-q", "-g", "terminal-overrides", "'xterm:colors=256'"
); );
// disable buffering of tmux output // disable buffering of tmux output
// TODO investigate if using capture pane after desync is faster // TODO investigate if using capture pane after desync is faster
args.push(";", "set-window-option", "c0-change-trigger", "0"); args.push(";", "setw", "c0-change-trigger", "0");
if (options.output) { if (options.output) {
args.push( args.push(
";", "set-option", "remain-on-exit", "on", ";", "set", "-q", "remain-on-exit", "on",
";", "set-window-option", "-g", "aggressive-resize", "on" ";", "setw", "-q", "-g", "aggressive-resize", "on"
); );
} }
if (options.detach && options.output) {
args.unshift("-C");
args.push(";", "list-panes", "-F", "c9-pid#{pane_pid}-");
}
if (options.detach) if (options.detach)
args.push(";", "detach"); args.push(";", "detach");
var quotedArgs = args.map(function(arg) {
return "'" + arg.replace(/'/g, "'\\''") + "'";
}).join(" ");
options.env["LD_LIBRARY_PATH"] = (options.env["LD_LIBRARY_PATH"] options.env["LD_LIBRARY_PATH"] = (options.env["LD_LIBRARY_PATH"]
? options.env["LD_LIBRARY_PATH"] + ":" : "") + "~/.c9/local/lib"; ? options.env["LD_LIBRARY_PATH"] + ":" : "") + "~/.c9/local/lib";
@ -1880,31 +1849,30 @@ module.exports = function setup(fsOptions) {
options.env["ISOUTPUTPANE"] = "1"; options.env["ISOUTPUTPANE"] = "1";
} }
if (options.command && !attach && !options.terminal) {
fs.writeFile(watchFile, "-1", "utf8", function(err){
if (err) {
fs.mkdir(dirname(watchFile), function(err){
if (err) return callback(err);
fs.writeFile(watchFile, "-1", "utf8", run);
});
}
else run();
});
}
else
run(); run();
function run(err){ function run(err){
if (err) return callback(err); if (err) return callback(err);
// Start PTY with TMUX, in a login shell (when not attached) if (options.detach && options.output) {
var EXEC = attach ? TMUX : BASH; _setDefaultEnv(options);
var ARGS = attach delete options.env.TMUX;
? args
: [ "-l", "-c", "'" + TMUX + "' " + quotedArgs + " 2> /dev/null"];
ptyspawn(EXEC, { return _execFile(TMUX, args, {
args: ARGS, args: args,
name: options.name,
cwd: options.cwd,
resolve: options.resolve,
env: options.env
}, function(err, stdout) {
var m = /c9-pid(\d+)-/.exec(stdout);
var pid = parseInt(m && m[1], 10);
callback(err, {pid: pid});
});
}
ptyspawn(TMUX, {
args: args,
name: options.name, name: options.name,
cols: options.cols, cols: options.cols,
rows: options.rows, rows: options.rows,
@ -1926,26 +1894,10 @@ module.exports = function setup(fsOptions) {
err.code = "EPERM"; err.code = "EPERM";
meta.pty.emit("data", err); meta.pty.emit("data", err);
} }
else if (data) { else if (data) {
// Fetch the PID if appropriate
if (options.detach && options.output) {
fetchPid(function(err, pid){
if (err) return callback(err);
meta.pid = pid.pid;
callback(null, meta);
});
if (process.platform != "win32")
meta.pty.kill();
}
meta.pty.removeListener("data", wait); meta.pty.removeListener("data", wait);
} }
}); });
if (options.detach && options.output)
return;
} }
// Return the pty // Return the pty
@ -2081,7 +2033,6 @@ module.exports = function setup(fsOptions) {
// Same as tmuxspawn but than spawns bash or other shell, for windows // Same as tmuxspawn but than spawns bash or other shell, for windows
var sessions = {}; var sessions = {};
function bashspawn(ignored, options, callback) { function bashspawn(ignored, options, callback) {
var watchFile = join(options.base || "", ".run_" + options.session + ".watch");
var session; var session;
function getSessionId(){ function getSessionId(){
@ -2092,7 +2043,9 @@ module.exports = function setup(fsOptions) {
// Fetch PID of a running process and return it // Fetch PID of a running process and return it
if (options.fetchpid) { if (options.fetchpid) {
session = sessions[options.session]; session = sessions[options.session];
setTimeout(function() {
callback(null, { pid: session && session.pty ? session.pty.pid : -1 }); callback(null, { pid: session && session.pty ? session.pty.pid : -1 });
}, 100); // workaround for late exit message from winpty
return; return;
} }
@ -2146,28 +2099,20 @@ module.exports = function setup(fsOptions) {
sessions[name] = session; sessions[name] = session;
if (!session.wait) session.wait = []; if (!session.wait) session.wait = [];
if (options.idle) if (options.idle) {
options.command = "echo '\033[2J\033[1;1H[Idle]'"; options.command = "echo '\033[2J\033[1;1H\033[01;34m[Idle]\033[0m'";
else if (options.command) } else if (options.command) {
options.command = "echo '\033[2J\033[1;1H';" + options.command; options.command = "echo '\033[2J\033[1;1H';" + options.command
+ ';printf "\033[01;30m\n\nProcess exited with code: $?\033[0m\n"';
}
// \"`#" + name + "`\"
if (options.command) { if (options.command) {
args.push("-c", "nodosfilewarning=1;" + options.command args.push("-c", "nodosfilewarning=1;" + options.command);
+ "; ([ -e '" + watchFile + "' ] && "
+ "rm '" + watchFile + "')");
var cmd = args[args.length - 1]; var cmd = args[args.length - 1];
args[args.length - 1] = '"' + cmd.replace(/"/g, '\\"') + '"'; args[args.length - 1] = '"' + cmd.replace(/"/g, '\\"') + '"';
} }
if (options.output) {
var path = watchFile[0] == "~"
? join(process.env.HOME, watchFile.substr(1))
: watchFile;
fs.writeFile(path, "-1", "utf8", run);
}
else
run(); run();
function run(err){ function run(err){
@ -2223,16 +2168,8 @@ module.exports = function setup(fsOptions) {
function execFile(executablePath, options, callback) { function execFile(executablePath, options, callback) {
if (isWin && execFileWin(executablePath, options, callback)) if (isWin && execFileWin(executablePath, options, callback))
return; return;
if (options.hasOwnProperty('env')) {
options.env.__proto__ = fsOptions.defaultEnv;
} else {
options.env = fsOptions.defaultEnv;
}
if (options.cwd && options.cwd.charAt(0) == "~")
options.cwd = options.env.HOME + options.cwd.substr(1);
if (transformPath && options.cwd) _setDefaultEnv(options);
options.cwd = transformPath(options.cwd);
resolvePath(executablePath, { resolvePath(executablePath, {
nocheck : 1, nocheck : 1,
@ -2259,10 +2196,9 @@ module.exports = function setup(fsOptions) {
function execFileWin(executablePath, options, callback) { function execFileWin(executablePath, options, callback) {
if (executablePath == "kill") { if (executablePath == "kill") {
var pid = options.args && options.args[0]; var pid = options.args && options.args[0];
console.error(pid, sessions)
Object.keys(sessions).some(function(key) { Object.keys(sessions).some(function(key) {
if (sessions[key].pid == pid && sessions[key].pty) { if (sessions[key].pid == pid && sessions[key].pty) {
console.error(sessions[key].pty.pid)
sessions[key].pty.killtree(-1); sessions[key].pty.killtree(-1);
return true; return true;
} }
@ -2272,6 +2208,25 @@ module.exports = function setup(fsOptions) {
} }
} }
function _setDefaultEnv(options) {
if (options.hasOwnProperty("env"))
options.env.__proto__ = fsOptions.defaultEnv;
else
options.env = fsOptions.defaultEnv;
// Pty is only reading from the object itself;
var env = {};
for (var prop in options.env)
env[prop] = options.env[prop];
options.env = env;
if (options.cwd && options.cwd.charAt(0) == "~")
options.cwd = options.env.HOME + options.cwd.substr(1);
if (transformPath && options.cwd)
options.cwd = transformPath(options.cwd);
}
function killtree(pid, options, callback) { function killtree(pid, options, callback) {
var code = options.code || options.graceful ? "SIGTERM" : "SIGKILL"; var code = options.code || options.graceful ? "SIGTERM" : "SIGKILL";

7
node_modules/vfs-socket/consumer.js wygenerowano vendored
Wyświetl plik

@ -273,15 +273,18 @@ function Consumer() {
function onExit(pid, code, signal) { function onExit(pid, code, signal) {
var process = proxyProcesses[pid]; var process = proxyProcesses[pid];
if (!process) return; if (!process) return;
// TODO: not delete proxy if close is going to be called later. // TODO: how can we confirm that both close and exit are always called
// but somehow do delete proxy if close won't be called later. if (process.closed)
delete proxyProcesses[pid]; delete proxyProcesses[pid];
process.exited = true;
process.emit("exit", code, signal); process.emit("exit", code, signal);
} }
function onProcessClose(pid) { function onProcessClose(pid) {
var process = proxyProcesses[pid]; var process = proxyProcesses[pid];
if (!process) return; if (!process) return;
if (process.exited)
delete proxyProcesses[pid]; delete proxyProcesses[pid];
process.closed = true;
process.emit("close"); process.emit("close");
} }
function onPtyKill(pid){ function onPtyKill(pid){

Wyświetl plik

@ -71,7 +71,7 @@
"c9.ide.find": "#35379124ca", "c9.ide.find": "#35379124ca",
"c9.ide.find.infiles": "#c132ad243c", "c9.ide.find.infiles": "#c132ad243c",
"c9.ide.find.replace": "#44772dd796", "c9.ide.find.replace": "#44772dd796",
"c9.ide.run.debug": "#3b76105b4e", "c9.ide.run.debug": "#a6bc1f422f",
"c9.automate": "#47e2c429c9", "c9.automate": "#47e2c429c9",
"c9.ide.ace.emmet": "#6dc4585e02", "c9.ide.ace.emmet": "#6dc4585e02",
"c9.ide.ace.gotoline": "#a8ff07c8f4", "c9.ide.ace.gotoline": "#a8ff07c8f4",
@ -102,7 +102,7 @@
"c9.ide.recentfiles": "#7c099abf40", "c9.ide.recentfiles": "#7c099abf40",
"c9.ide.remote": "#301d2ab519", "c9.ide.remote": "#301d2ab519",
"c9.ide.processlist": "#2b12cd1bdd", "c9.ide.processlist": "#2b12cd1bdd",
"c9.ide.run": "#f360984649", "c9.ide.run": "#4d73c19553",
"c9.ide.run.build": "#0598fff697", "c9.ide.run.build": "#0598fff697",
"c9.ide.run.debug.xdebug": "#61dcbd0180", "c9.ide.run.debug.xdebug": "#61dcbd0180",
"c9.ide.save": "#4a4a60a004", "c9.ide.save": "#4a4a60a004",

Wyświetl plik

@ -100,12 +100,6 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "ace/test/asse
return tab.pane.aml.getPage("editor::" + tab.editorType).$ext; return tab.pane.aml.getPage("editor::" + tab.editorType).$ext;
}); });
predictor.on("mispredict", function(e) {
console.error("MISPREDICTED", e)
delete e.session;
throw new Error("MISPREDICTED: " + JSON.stringify(e));
});
describe('terminal.predict_echo', function() { describe('terminal.predict_echo', function() {
this.timeout(30000); this.timeout(30000);
@ -140,18 +134,28 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "ace/test/asse
session = editor.ace.getSession().c9session; session = editor.ace.getSession().c9session;
send = session.send; send = session.send;
if (peek(-2) === "$") // maybe there already was a prompt setTimeout(init);
return init();
afterPrompt(function() { setTimeout(init); });
function init() { function init() {
afterPrompt(function() { setTimeout(start); });
// Make sure we have a prompt with a dollar for tests // Make sure we have a prompt with a dollar for tests
afterPrompt(function() { done() }); // And terminal won't send rename commands in the middle of the test
editor.ace.onTextInput("PS1='. $ '\n"); // TODO: do we need to handle rename sequence in predict_echo instead?
//editor.ace.onTextInput("ssh lennart\n"); editor.ace.onTextInput("PS1='. $ ';"
//editor.ace.onTextInput("ssh ubuntu@ci.c9.io\n"); + "tmux setw automatic-rename off;"
+ "printf '\\x1b]0;predict echo\\x07'\n");
// editor.ace.onTextInput("ssh lennart\n");
// editor.ace.onTextInput("ssh ubuntu@ci.c9.io\n");
} }
}); });
function start() {
predictor.on("mispredict", function(e) {
console.error("MISPREDICTED", e)
delete e.session;
throw new Error("MISPREDICTED: " + JSON.stringify(e));
});
setTimeout(done)
}
} }
function peek(offset) { function peek(offset) {
@ -528,7 +532,7 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "ace/test/asse
afterPredict(":", function() { afterPredict(":", function() {
afterPrompt(done); afterPrompt(done);
send("\r"); send("\r");
}) });
sendAll("echo".split(""), function() { sendAll("echo".split(""), function() {
send(INPUT_BACKSPACE); send(INPUT_BACKSPACE);
send(INPUT_BACKSPACE); send(INPUT_BACKSPACE);

Wyświetl plik

@ -668,6 +668,7 @@ define(function(require, exports, module) {
var queue = ""; var queue = "";
var warned = false; var warned = false;
var timer = null; var timer = null;
var initialConnect = true;
function send(data) { function send(data) {
if (!(c9.status & c9.NETWORK)) if (!(c9.status & c9.NETWORK))
@ -680,7 +681,7 @@ define(function(require, exports, module) {
timer = setTimeout(function() { timer = setTimeout(function() {
timer = null; timer = null;
if (!session.connected) if (!session.connected)
return warnConnection(); return initialConnect || warnConnection();
// Send data to stdin of tmux process // Send data to stdin of tmux process
session.pty.write(queue); session.pty.write(queue);
queue = ""; queue = "";
@ -746,6 +747,11 @@ define(function(require, exports, module) {
tab: session.tab tab: session.tab
}); });
loadHistory(session); loadHistory(session);
initialConnect = false;
if (queue) {
session.pty.write(queue);
queue = "";
}
} }
}); });
}); });

Wyświetl plik

@ -77,13 +77,18 @@ module.exports = function(c9, proc, installPath, shell) {
meta.process.stderr.on("data", function(data) { meta.process.stderr.on("data", function(data) {
errBuffer += data.toString(); errBuffer += data.toString();
}); });
meta.process.on("exit", function() { meta.process.on("close", function() {
if (!buffer && !errBuffer && options.retries < 4) { if (!buffer && !errBuffer && options.retries < 4) {
// tmux doesn't produce any output if two instances are invoked at the same time // tmux doesn't produce any output if two instances are invoked at the same time
return setTimeout(function() { return setTimeout(function() {
getOutputHistory(options, cb); getOutputHistory(options, cb);
}, options.retries * 100 + 300); }, options.retries * 100 + 300);
} }
if (buffer) {
var i = buffer.search(/\x1b\[1mPane is dead\x1b\[0m\s*$/);
if (i != -1)
buffer = buffer.slice(0, i).replace(/\s*$/, "\n");
}
cb(errBuffer, buffer); cb(errBuffer, buffer);
}); });
}); });