kopia lustrzana https://github.com/c9/core
Merge pull request +10204 from c9/tmuxspawn
Fix tmux getting into restart loop when profile prints somethingpull/223/head
commit
584f9ea55b
|
@ -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";
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
Ładowanie…
Reference in New Issue