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ęć

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

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

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

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

Wyświetl plik

@ -71,7 +71,7 @@
"c9.ide.find": "#35379124ca",
"c9.ide.find.infiles": "#c132ad243c",
"c9.ide.find.replace": "#44772dd796",
"c9.ide.run.debug": "#3b76105b4e",
"c9.ide.run.debug": "#a6bc1f422f",
"c9.automate": "#47e2c429c9",
"c9.ide.ace.emmet": "#6dc4585e02",
"c9.ide.ace.gotoline": "#a8ff07c8f4",
@ -102,7 +102,7 @@
"c9.ide.recentfiles": "#7c099abf40",
"c9.ide.remote": "#301d2ab519",
"c9.ide.processlist": "#2b12cd1bdd",
"c9.ide.run": "#f360984649",
"c9.ide.run": "#4d73c19553",
"c9.ide.run.build": "#0598fff697",
"c9.ide.run.debug.xdebug": "#61dcbd0180",
"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;
});
predictor.on("mispredict", function(e) {
console.error("MISPREDICTED", e)
delete e.session;
throw new Error("MISPREDICTED: " + JSON.stringify(e));
});
describe('terminal.predict_echo', function() {
this.timeout(30000);
@ -140,18 +134,28 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "ace/test/asse
session = editor.ace.getSession().c9session;
send = session.send;
if (peek(-2) === "$") // maybe there already was a prompt
return init();
afterPrompt(function() { setTimeout(init); });
setTimeout(init);
function init() {
afterPrompt(function() { setTimeout(start); });
// Make sure we have a prompt with a dollar for tests
afterPrompt(function() { done() });
editor.ace.onTextInput("PS1='. $ '\n");
//editor.ace.onTextInput("ssh lennart\n");
//editor.ace.onTextInput("ssh ubuntu@ci.c9.io\n");
// And terminal won't send rename commands in the middle of the test
// TODO: do we need to handle rename sequence in predict_echo instead?
editor.ace.onTextInput("PS1='. $ ';"
+ "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) {
@ -528,7 +532,7 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "ace/test/asse
afterPredict(":", function() {
afterPrompt(done);
send("\r");
})
});
sendAll("echo".split(""), function() {
send(INPUT_BACKSPACE);
send(INPUT_BACKSPACE);

Wyświetl plik

@ -668,6 +668,7 @@ define(function(require, exports, module) {
var queue = "";
var warned = false;
var timer = null;
var initialConnect = true;
function send(data) {
if (!(c9.status & c9.NETWORK))
@ -680,7 +681,7 @@ define(function(require, exports, module) {
timer = setTimeout(function() {
timer = null;
if (!session.connected)
return warnConnection();
return initialConnect || warnConnection();
// Send data to stdin of tmux process
session.pty.write(queue);
queue = "";
@ -746,6 +747,11 @@ define(function(require, exports, module) {
tab: session.tab
});
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) {
errBuffer += data.toString();
});
meta.process.on("exit", function() {
meta.process.on("close", function() {
if (!buffer && !errBuffer && options.retries < 4) {
// tmux doesn't produce any output if two instances are invoked at the same time
return setTimeout(function() {
getOutputHistory(options, cb);
}, 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);
});
});