Skip to content

Commit

Permalink
Merge pull request rstudio#919 from rstudio/feature/terminal
Browse files Browse the repository at this point in the history
Partial terminal support for session suspend resume
  • Loading branch information
jmcphers authored Dec 6, 2016
2 parents bd14bb9 + 90948da commit 0fde5d3
Show file tree
Hide file tree
Showing 18 changed files with 296 additions and 136 deletions.
119 changes: 84 additions & 35 deletions src/cpp/session/SessionConsoleProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,46 +49,54 @@ namespace {
} // anonymous namespace

const int kDefaultMaxOutputLines = 500;
const int kNoTerminal = 0; // terminal sequence number for a non-terminal

ConsoleProcess::ConsoleProcess()
: dialog_(false), showOnOutput_(false), interactionMode_(InteractionNever),
maxOutputLines_(kDefaultMaxOutputLines), started_(true),
interrupt_(false), newCols_(-1), newRows_(-1), terminalSequence_(0),
outputBuffer_(OUTPUT_BUFFER_SIZE)
allowRestart_(false), outputBuffer_(OUTPUT_BUFFER_SIZE)
{
regexInit();

// When we retrieve from outputBuffer, we only want complete lines. Add a
// dummy \n so we can tell the first line is a complete line.
outputBuffer_.push_back('\n');
}

ConsoleProcess::ConsoleProcess(const std::string& command,
const core::system::ProcessOptions& options,
const std::string& caption,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode interactionMode,
int maxOutputLines)
: command_(command), options_(options), caption_(caption), dialog_(dialog),
showOnOutput_(false),
interactionMode_(interactionMode), maxOutputLines_(maxOutputLines),
started_(false), interrupt_(false), newCols_(-1), newRows_(-1), terminalSequence_(0),
started_(false), interrupt_(false), newCols_(-1), newRows_(-1),
terminalSequence_(terminalSequence), allowRestart_(allowRestart),
outputBuffer_(OUTPUT_BUFFER_SIZE)
{
commonInit();
}


ConsoleProcess::ConsoleProcess(const std::string& program,
const std::vector<std::string>& args,
const core::system::ProcessOptions& options,
const std::string& caption,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode interactionMode,
int maxOutputLines)
: program_(program), args_(args), options_(options), caption_(caption), dialog_(dialog),
showOnOutput_(false),
interactionMode_(interactionMode), maxOutputLines_(maxOutputLines),
started_(false), interrupt_(false), newCols_(-1), newRows_(-1), terminalSequence_(0),
started_(false), interrupt_(false), newCols_(-1), newRows_(-1),
terminalSequence_(terminalSequence), allowRestart_(allowRestart),
outputBuffer_(OUTPUT_BUFFER_SIZE)
{
commonInit();
Expand All @@ -97,21 +105,22 @@ ConsoleProcess::ConsoleProcess(const std::string& program,
ConsoleProcess::ConsoleProcess(const std::string& command,
const core::system::ProcessOptions& options,
const std::string& caption,
const std::string& terminalHandle,
int terminalSequence,
bool allowRestart,
const std::string& handle,
bool dialog,
InteractionMode interactionMode,
int maxOutputLines)
: command_(command), options_(options), caption_(caption), dialog_(dialog),
showOnOutput_(false),
interactionMode_(interactionMode), maxOutputLines_(maxOutputLines),
started_(false), interrupt_(false), newCols_(-1), newRows_(-1),
terminalHandle_(terminalHandle), terminalSequence_(terminalSequence),
handle_(handle), started_(false), interrupt_(false), newCols_(-1), newRows_(-1),
terminalSequence_(terminalSequence), allowRestart_(allowRestart),
outputBuffer_(OUTPUT_BUFFER_SIZE)
{
commonInit();
}

void ConsoleProcess::regexInit()
{
controlCharsPattern_ = boost::regex("[\\r\\b]");
Expand All @@ -122,12 +131,9 @@ void ConsoleProcess::commonInit()
{
regexInit();

handle_ = core::system::generateUuid(false);
if (handle_.empty())
handle_ = core::system::generateUuid(false);

// TODO (gary) don't think I need a separate terminal handle, the process
// handle should do the trick; I won't unwire it all until I'm sure
terminalHandle_ = handle_;

// always redirect stderr to stdout so output is interleaved
options_.redirectStdErrToStdOut = true;

Expand Down Expand Up @@ -297,7 +303,7 @@ void ConsoleProcess::appendToOutputBuffer(const std::string &str)
// in the SessionInfo; this will be stored separately, keyed by
// Terminal Handle; as a prelude to that stop storing the old way
// if we have the terminal handle
if (terminalHandle_.empty())
if (terminalSequence_ == kNoTerminal)
std::copy(str.begin(), str.end(), std::back_inserter(outputBuffer_));
}

Expand Down Expand Up @@ -410,6 +416,12 @@ void ConsoleProcess::handleConsolePrompt(core::system::ProcessOperations& ops,
ClientEvent(client_events::kConsoleProcessPrompt, data));
}

void ConsoleProcess::onSuspend()
{
if (started_ && allowRestart_)
started_ = false;
}

void ConsoleProcess::onExit(int exitCode)
{
exitCode_.reset(exitCode);
Expand Down Expand Up @@ -439,8 +451,9 @@ core::json::Object ConsoleProcess::toJson() const
result["exit_code"] = json::Value();

// newly added in v1.1
result["terminal_handle"] = terminalHandle_;
result["terminal_sequence"] = terminalSequence_;
result["allow_restart"] = allowRestart_;
result["started"] = started_;
return result;
}

Expand Down Expand Up @@ -482,15 +495,31 @@ boost::shared_ptr<ConsoleProcess> ConsoleProcess::fromJson(
// Newly added in v1.1
Error error = json::readObject(
obj,
"terminal_handle", &pProc->terminalHandle_,
"terminal_sequence", &pProc->terminalSequence_);
if (error)
{
// Possibly unarchiving a pre 1.1 session; ensure defaults are set
// and continue
LOG_ERROR(error);
pProc->terminalHandle_.clear();
pProc->terminalSequence_ = 0;
pProc->terminalSequence_ = kNoTerminal;
return pProc;
}

// Newly added during work-in-progress on 1.1
// TODO (gary) could flatten this to a single readObject before release
error = json::readObject(obj,
"allow_restart", &pProc->allowRestart_,
"started", &pProc->started_);
if (error)
{
// Possibly unarchiving 1.1 work-in-progress session; match
// previous behavior and continue
if (pProc->terminalSequence_ != kNoTerminal)
{
pProc->allowRestart_ = true;
pProc->started_ = false;
}
LOG_ERROR(error);
}

return pProc;
Expand Down Expand Up @@ -636,6 +665,8 @@ boost::shared_ptr<ConsoleProcess> ConsoleProcess::create(
const std::string& command,
core::system::ProcessOptions options,
const std::string& caption,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode interactionMode,
int maxOutputLines)
Expand All @@ -645,6 +676,8 @@ boost::shared_ptr<ConsoleProcess> ConsoleProcess::create(
new ConsoleProcess(command,
options,
caption,
terminalSequence,
allowRestart,
dialog,
interactionMode,
maxOutputLines));
Expand All @@ -657,6 +690,8 @@ boost::shared_ptr<ConsoleProcess> ConsoleProcess::create(
const std::vector<std::string>& args,
core::system::ProcessOptions options,
const std::string& caption,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode interactionMode,
int maxOutputLines)
Expand All @@ -667,42 +702,55 @@ boost::shared_ptr<ConsoleProcess> ConsoleProcess::create(
args,
options,
caption,
terminalSequence,
allowRestart,
dialog,
interactionMode,
maxOutputLines));
s_procs[ptrProc->handle()] = ptrProc;
return ptrProc;
}

// supports reattaching to a running process, or creating a new process with
// previously used handle
boost::shared_ptr<ConsoleProcess> ConsoleProcess::create(
const std::string& command,
core::system::ProcessOptions options,
const std::string& caption,
const std::string& terminalHandle,
const int terminalSequence,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode interactionMode,
int maxOutputLines)
{
// return existing ConsoleProcess
ProcTable::const_iterator pos = s_procs.find(terminalHandle);
if (pos != s_procs.end())
if (allowRestart && !terminalHandle.empty())
{
return pos->second;
// return existing ConsoleProcess if it is still running
ProcTable::const_iterator pos = s_procs.find(terminalHandle);
if (pos != s_procs.end() && pos->second->isStarted())
{
// Jiggle the size of the pseudo-terminal, this will force the app
// to refresh itself; this does rely on the host performing a second
// resize to the actual available size. Clumsy, but so far this is
// the best I've come up with.
pos->second->resize(25, 5);
return pos->second;
}

// Create new process with previously used handle
options.terminateChildren = true;
boost::shared_ptr<ConsoleProcess> ptrProc(
new ConsoleProcess(command, options, caption, terminalSequence,
allowRestart, terminalHandle, dialog,
interactionMode, maxOutputLines));
s_procs[ptrProc->handle()] = ptrProc;
return ptrProc;
}

options.terminateChildren = true;
boost::shared_ptr<ConsoleProcess> ptrProc(
new ConsoleProcess(command,
options,
caption,
terminalHandle,
terminalSequence,
dialog,
interactionMode,
maxOutputLines));
s_procs[ptrProc->handle()] = ptrProc;
return ptrProc;
// otherwise create a new one
return create(command, options, caption, terminalSequence,
allowRestart, dialog, interactionMode, maxOutputLines);
}

void PasswordManager::attach(
Expand Down Expand Up @@ -840,6 +888,7 @@ void onSuspend(core::Settings* pSettings)
it != s_procs.end();
it++)
{
it->second->onSuspend();
array.push_back(it->second->toJson());
}

Expand Down
31 changes: 22 additions & 9 deletions src/cpp/session/include/session/SessionConsoleProcess.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ enum InteractionMode
};

extern const int kDefaultMaxOutputLines;
extern const int kNoTerminal;

class ConsoleProcess : boost::noncopyable,
public boost::enable_shared_from_this<ConsoleProcess>
Expand All @@ -58,29 +59,34 @@ class ConsoleProcess : boost::noncopyable,
const std::string& command,
const core::system::ProcessOptions& options,
const std::string& caption,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode mode,
int maxOutputLines);

ConsoleProcess(
const std::string& program,
const std::vector<std::string>& args,
const core::system::ProcessOptions& options,
const std::string& caption,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode mode,
int maxOutputLines);

ConsoleProcess(
const std::string& command,
const core::system::ProcessOptions& options,
const std::string& caption,
const std::string& terminalHandle,
int terminalSequence,
bool allowRestart,
const std::string& handle,
bool dialog,
InteractionMode mode,
int maxOutputLines);

void regexInit();
void commonInit();

Expand Down Expand Up @@ -110,6 +116,8 @@ class ConsoleProcess : boost::noncopyable,
const std::string& command,
core::system::ProcessOptions options,
const std::string& caption,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode mode,
int maxOutputLines = kDefaultMaxOutputLines);
Expand All @@ -119,6 +127,8 @@ class ConsoleProcess : boost::noncopyable,
const std::vector<std::string>& args,
core::system::ProcessOptions options,
const std::string& caption,
int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode mode,
int maxOutputLines = kDefaultMaxOutputLines);
Expand All @@ -129,6 +139,7 @@ class ConsoleProcess : boost::noncopyable,
const std::string& caption,
const std::string& terminalHandle,
const int terminalSequence,
bool allowRestart,
bool dialog,
InteractionMode mode,
int maxOutputLines = kDefaultMaxOutputLines);
Expand All @@ -151,6 +162,8 @@ class ConsoleProcess : boost::noncopyable,
void enqueInput(const Input& input);
void interrupt();
void resize(int cols, int rows);
void onSuspend();
bool isStarted() { return started_; }

void setShowOnOutput(bool showOnOutput) { showOnOutput_ = showOnOutput; }

Expand Down Expand Up @@ -199,13 +212,14 @@ class ConsoleProcess : boost::noncopyable,
int newCols_; // -1 = no change
int newRows_; // -1 = no change

// The handle of an associated terminal
std::string terminalHandle_;

// The sequence number of the associated terminal; used to control display
// order of terminal tabs
// order of terminal tabs; constant 'kNoTerminal' indicates a non-terminal
int terminalSequence_;

// Whether a ConsoleProcess object should start a new process on resume after
// its process has been killed by a suspend.
bool allowRestart_;

// Pending input (writes or ptyInterrupts)
std::queue<Input> inputQueue_;

Expand All @@ -218,7 +232,6 @@ class ConsoleProcess : boost::noncopyable,
boost::function<bool(const std::string&, Input*)> onPrompt_;
boost::signal<void(int)> onExit_;


// regex for prompt detection
boost::regex controlCharsPattern_;
boost::regex promptPattern_;
Expand Down
4 changes: 3 additions & 1 deletion src/cpp/session/modules/SessionDependencies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,9 @@ Error installDependencies(const json::JsonRpcRequest& request,
args,
options,
"Installing Packages",
true,
console_process::kNoTerminal,
false /*allowRestart*/,
true /*dialog*/,
console_process::InteractionNever);

// return console process
Expand Down
Loading

0 comments on commit 0fde5d3

Please sign in to comment.