Shut down the unix-adapter when the child process exits.

- In the agent, poll for the process exit at the same time we pull for
   output.  Once the child exits, record the exit code and close the data
   pipe.

 - In the unix-adapter, shut the program down once the input or output
   handlers abort.  Before exiting, query the agent for the exit code.

 - Also: in the unix-adapter, apparently receiving the SIGWINCH signal can
   interrupt both select and the InputHandler's read system call.  I hope
   it doesn't affect the blocking Win32 APIs, but I'm not really sure.
This commit is contained in:
Ryan Prichard 2012-02-20 03:30:43 -08:00
parent 90164c34a9
commit 00a6c3b90e
5 changed files with 94 additions and 24 deletions

View File

@ -5,7 +5,8 @@ struct AgentMsg
{
enum Type {
StartProcess,
SetSize
SetSize,
GetExitCode
};
};

View File

@ -26,7 +26,8 @@ Agent::Agent(const QString &controlPipeName,
QObject(parent),
m_terminal(NULL),
m_timer(NULL),
m_autoShutDown(false),
m_childProcess(NULL),
m_childExitCode(-1),
m_syncCounter(0)
{
m_bufferData = new CHAR_INFO[BUFFER_LINE_COUNT][MAX_CONSOLE_WIDTH];
@ -44,6 +45,7 @@ Agent::Agent(const QString &controlPipeName,
resetConsoleTracking(false);
connect(m_controlSocket, SIGNAL(readyRead()), SLOT(controlSocketReadyRead()));
connect(m_controlSocket, SIGNAL(disconnected()), SLOT(socketDisconnected()));
connect(m_dataSocket, SIGNAL(readyRead()), SLOT(dataSocketReadyRead()));
m_timer = new QTimer(this);
@ -68,7 +70,6 @@ QLocalSocket *Agent::makeSocket(const QString &pipeName)
if (!socket->waitForConnected())
qFatal("Could not connect to %s", pipeName.toStdString().c_str());
socket->setReadBufferSize(64 * 1024);
connect(socket, SIGNAL(disconnected()), SLOT(socketDisconnected()));
return socket;
}
@ -108,18 +109,25 @@ void Agent::controlSocketReadyRead()
void Agent::handlePacket(ReadBuffer &packet)
{
int type = packet.getInt();
int32_t result = -1;
switch (type) {
case AgentMsg::StartProcess:
handleStartProcessPacket(packet);
result = handleStartProcessPacket(packet);
break;
case AgentMsg::SetSize:
handleSetSizePacket(packet);
result = handleSetSizePacket(packet);
break;
case AgentMsg::GetExitCode:
packet.assertEof();
result = m_childExitCode;
}
m_controlSocket->write((char*)&result, sizeof(result));
}
void Agent::handleStartProcessPacket(ReadBuffer &packet)
int Agent::handleStartProcessPacket(ReadBuffer &packet)
{
assert(m_childProcess == NULL);
std::wstring program = packet.getWString();
std::wstring cmdline = packet.getWString();
std::wstring cwd = packet.getWString();
@ -149,15 +157,23 @@ void Agent::handleStartProcessPacket(ReadBuffer &packet)
(LPVOID)envArg, cwdArg, &sui, &pi);
Trace("cp: %s %d", (ret ? "success" : "fail"), (int)pi.dwProcessId);
if (ret) {
CloseHandle(pi.hThread);
m_childProcess = pi.hProcess;
}
// TODO: report success/failure to client
return ret ? 0 : GetLastError();
}
void Agent::handleSetSizePacket(ReadBuffer &packet)
int Agent::handleSetSizePacket(ReadBuffer &packet)
{
int cols = packet.getInt();
int rows = packet.getInt();
packet.assertEof();
resizeWindow(cols, rows);
return 0;
}
void Agent::dataSocketReadyRead()
@ -189,7 +205,19 @@ void Agent::socketDisconnected()
void Agent::pollTimeout()
{
scrapeOutput();
if (m_dataSocket->state() == QLocalSocket::ConnectedState)
scrapeOutput();
if (m_childProcess != NULL) {
if (WaitForSingleObject(m_childProcess, 0) == WAIT_OBJECT_0) {
DWORD exitCode;
if (GetExitCodeProcess(m_childProcess, &exitCode))
m_childExitCode = exitCode;
CloseHandle(m_childProcess);
m_childProcess = NULL;
m_dataSocket->disconnectFromServer();
}
}
}
// Detect window movement. If the window moves down (presumably as a

View File

@ -33,8 +33,8 @@ signals:
private slots:
void controlSocketReadyRead();
void handlePacket(ReadBuffer &packet);
void handleStartProcessPacket(ReadBuffer &packet);
void handleSetSizePacket(ReadBuffer &packet);
int handleStartProcessPacket(ReadBuffer &packet);
int handleSetSizePacket(ReadBuffer &packet);
void dataSocketReadyRead();
void socketDisconnected();
void pollTimeout();
@ -56,8 +56,8 @@ private:
QLocalSocket *m_dataSocket;
Terminal *m_terminal;
QTimer *m_timer;
bool m_autoShutDown;
HANDLE m_childProcess;
int m_childExitCode;
int m_syncRow;
int m_syncCounter;

View File

@ -257,6 +257,15 @@ static void writePacket(pconsole_t *pc, const WriteBuffer &packet)
assert(success && actual == payloadSize);
}
static int32_t readInt32(pconsole_t *pc)
{
int32_t result;
DWORD actual;
BOOL success = ReadFile(pc->controlPipe, &result, sizeof(int32_t), &actual, NULL);
assert(success && actual == sizeof(int32_t));
return result;
}
PCONSOLE_API int pconsole_start_process(pconsole_t *pc,
const wchar_t *appname,
const wchar_t *cmdline,
@ -279,7 +288,15 @@ PCONSOLE_API int pconsole_start_process(pconsole_t *pc,
}
packet.putWString(envStr);
writePacket(pc, packet);
// TODO: return success/fail...
return readInt32(pc);
}
PCONSOLE_API int pconsole_get_exit_code(pconsole_t *pc)
{
WriteBuffer packet;
packet.putInt(AgentMsg::GetExitCode);
writePacket(pc, packet);
return readInt32(pc);
}
PCONSOLE_API HANDLE pconsole_get_data_pipe(pconsole_t *pc)
@ -294,6 +311,7 @@ PCONSOLE_API int pconsole_set_size(pconsole_t *pc, int cols, int rows)
packet.putInt(cols);
packet.putInt(rows);
writePacket(pc, packet);
return readInt32(pc);
}
PCONSOLE_API void pconsole_close(pconsole_t *pc)

View File

@ -14,6 +14,7 @@
static int signalWriteFd;
static volatile bool ioHandlerDied;
// Put the input terminal into non-blocking non-canonical mode.
@ -125,12 +126,19 @@ void *OutputHandler::threadProc(void *pvthis)
if (!ret || amount == 0)
break;
// TODO: partial writes?
int written = write(STDOUT_FILENO, buffer, amount);
// I don't know if this write can be interrupted or not, but handle it
// just in case.
int written;
do {
written = write(STDOUT_FILENO, buffer, amount);
} while (written == -1 && errno == EINTR);
if (written != amount)
break;
}
delete [] buffer;
CloseHandle(event);
ioHandlerDied = true;
writeToSignalFd();
return NULL;
}
@ -159,6 +167,11 @@ void *InputHandler::threadProc(void *pvthis)
char *buffer = new char[bufferSize];
while (true) {
int amount = read(STDIN_FILENO, buffer, bufferSize);
if (amount == -1 && errno == EINTR) {
// Apparently, this read is interrupted on Cygwin 1.7 by a SIGWINCH
// signal even though I set the SA_RESTART flag on the handler.
continue;
}
if (amount <= 0)
break;
DWORD written;
@ -177,6 +190,8 @@ void *InputHandler::threadProc(void *pvthis)
}
delete [] buffer;
CloseHandle(event);
ioHandlerDied = true;
writeToSignalFd();
return NULL;
}
@ -223,11 +238,22 @@ int main()
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(signalReadFd, &readfds);
if (select(signalReadFd + 1, &readfds, NULL, NULL, NULL) < 0) {
if (select(signalReadFd + 1, &readfds, NULL, NULL, NULL) < 0 &&
errno != EINTR) {
perror("select failed");
exit(1);
}
// Discard any data in the signal pipe.
{
char tmpBuf[256];
int amount = read(signalReadFd, tmpBuf, sizeof(tmpBuf));
if (amount == 0 || amount < 0 && errno != EAGAIN) {
perror("error reading internal signal fd");
exit(1);
}
}
// Check for terminal resize.
{
winsize sz2;
@ -238,17 +264,14 @@ int main()
}
}
// Discard any data in the signal pipe.
char tmpBuf[256];
int amount = read(signalReadFd, tmpBuf, sizeof(tmpBuf));
if (amount == 0 || amount < 0 && errno != EAGAIN) {
perror("error reading internal signal fd");
exit(1);
}
// Check for an I/O handler shutting down (possibly indicating that the
// child process has exited).
if (ioHandlerDied)
break;
}
// TODO: Get the pconsole child exit code and exit with it.
int exitCode = pconsole_get_exit_code(pconsole);
restoreTerminalMode(mode);
return 0;
return exitCode;
}