/*-------------------------------------------------------------------------
* drawElements Quality Program Execution Server
* ---------------------------------------------
*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief TestProcess implementation for Win32.
*//*--------------------------------------------------------------------*/
#include "xsWin32TestProcess.hpp"
#include "deFilePath.hpp"
#include "deString.h"
#include "deMemory.h"
#include "deClock.h"
#include "deFile.h"
#include <sstream>
#include <string.h>
using std::string;
using std::vector;
namespace xs
{
enum
{
MAX_OLD_LOGFILE_DELETE_ATTEMPTS = 20, //!< How many times execserver tries to delete old log file
LOGFILE_DELETE_SLEEP_MS = 50 //!< Sleep time (in ms) between log file delete attempts
};
namespace win32
{
// Error
static std::string formatErrMsg (DWORD error, const char* msg)
{
std::ostringstream str;
LPSTR msgBuf;
#if defined(UNICODE)
# error Unicode not supported.
#endif
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msgBuf, 0, DE_NULL) > 0)
str << msg << ", error " << error << ": " << msgBuf;
else
str << msg << ", error " << error;
return str.str();
}
Error::Error (DWORD error, const char* msg)
: std::runtime_error(formatErrMsg(error, msg))
, m_error (error)
{
}
// Event
Event::Event (bool manualReset, bool initialState)
: m_handle(0)
{
m_handle = CreateEvent(NULL, manualReset ? TRUE : FALSE, initialState ? TRUE : FALSE, NULL);
if (!m_handle)
throw Error(GetLastError(), "CreateEvent() failed");
}
Event::~Event (void)
{
CloseHandle(m_handle);
}
void Event::setSignaled (void)
{
if (!SetEvent(m_handle))
throw Error(GetLastError(), "SetEvent() failed");
}
void Event::reset (void)
{
if (!ResetEvent(m_handle))
throw Error(GetLastError(), "ResetEvent() failed");
}
// CaseListWriter
CaseListWriter::CaseListWriter (void)
: m_dst (INVALID_HANDLE_VALUE)
, m_cancelEvent (true, false)
{
}
CaseListWriter::~CaseListWriter (void)
{
}
void CaseListWriter::start (const char* caseList, HANDLE dst)
{
DE_ASSERT(!isStarted());
m_dst = dst;
int caseListSize = (int)strlen(caseList)+1;
m_caseList.resize(caseListSize);
std::copy(caseList, caseList+caseListSize, m_caseList.begin());
de::Thread::start();
}
void CaseListWriter::run (void)
{
try
{
Event ioEvent (true, false); // Manual reset, non-signaled state.
HANDLE waitHandles[] = { ioEvent.getHandle(), m_cancelEvent.getHandle() };
OVERLAPPED overlapped;
int curPos = 0;
deMemset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = ioEvent.getHandle();
while (curPos < (int)m_caseList.size())
{
const int maxWriteSize = 4096;
const int numToWrite = de::min(maxWriteSize, (int)m_caseList.size() - curPos);
DWORD waitRes = 0;
if (!WriteFile(m_dst, &m_caseList[curPos], (DWORD)numToWrite, NULL, &overlapped))
{
DWORD err = GetLastError();
if (err != ERROR_IO_PENDING)
throw Error(err, "WriteFile() failed");
}
waitRes = WaitForMultipleObjects(DE_LENGTH_OF_ARRAY(waitHandles), &waitHandles[0], FALSE, INFINITE);
if (waitRes == WAIT_OBJECT_0)
{
DWORD numBytesWritten = 0;
// \note GetOverlappedResult() will fail with ERROR_IO_INCOMPLETE if IO event is not complete (should be).
if (!GetOverlappedResult(m_dst, &overlapped, &numBytesWritten, FALSE))
throw Error(GetLastError(), "GetOverlappedResult() failed");
if (numBytesWritten == 0)
throw Error(GetLastError(), "Writing to pipe failed (pipe closed?)");
curPos += (int)numBytesWritten;
}
else if (waitRes == WAIT_OBJECT_0 + 1)
{
// Cancel.
if (!CancelIo(m_dst))
throw Error(GetLastError(), "CancelIo() failed");
break;
}
else
throw Error(GetLastError(), "WaitForMultipleObjects() failed");
}
}
catch (const std::exception& e)
{
// \todo [2013-08-13 pyry] What to do about this?
printf("win32::CaseListWriter::run(): %s\n", e.what());
}
}
void CaseListWriter::stop (void)
{
if (!isStarted())
return; // Nothing to do.
m_cancelEvent.setSignaled();
// Join thread.
join();
m_cancelEvent.reset();
m_dst = INVALID_HANDLE_VALUE;
}
// FileReader
FileReader::FileReader (ThreadedByteBuffer* dst)
: m_dstBuf (dst)
, m_handle (INVALID_HANDLE_VALUE)
, m_cancelEvent (false, false)
{
}
FileReader::~FileReader (void)
{
}
void FileReader::start (HANDLE file)
{
DE_ASSERT(!isStarted());
m_handle = file;
de::Thread::start();
}
void FileReader::run (void)
{
try
{
Event ioEvent (true, false); // Manual reset, not signaled state.
HANDLE waitHandles[] = { ioEvent.getHandle(), m_cancelEvent.getHandle() };
OVERLAPPED overlapped;
std::vector<deUint8> tmpBuf (FILEREADER_TMP_BUFFER_SIZE);
deUint64 offset = 0; // Overlapped IO requires manual offset keeping.
deMemset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = ioEvent.getHandle();
for (;;)
{
DWORD numBytesRead = 0;
DWORD waitRes;
overlapped.Offset = (DWORD)(offset & 0xffffffffu);
overlapped.OffsetHigh = (DWORD)(offset >> 32);
if (!ReadFile(m_handle, &tmpBuf[0], (DWORD)tmpBuf.size(), NULL, &overlapped))
{
DWORD err = GetLastError();
if (err == ERROR_BROKEN_PIPE)
break;
else if (err == ERROR_HANDLE_EOF)
{
if (m_dstBuf->isCanceled())
break;
deSleep(FILEREADER_IDLE_SLEEP);
if (m_dstBuf->isCanceled())
break;
else
continue;
}
else if (err != ERROR_IO_PENDING)
throw Error(err, "ReadFile() failed");
}
waitRes = WaitForMultipleObjects(DE_LENGTH_OF_ARRAY(waitHandles), &waitHandles[0], FALSE, INFINITE);
if (waitRes == WAIT_OBJECT_0)
{
// \note GetOverlappedResult() will fail with ERROR_IO_INCOMPLETE if IO event is not complete (should be).
if (!GetOverlappedResult(m_handle, &overlapped, &numBytesRead, FALSE))
{
DWORD err = GetLastError();
if (err == ERROR_HANDLE_EOF)
{
// End of file - for now.
// \note Should check for end of buffer here, or otherwise may end up in infinite loop.
if (m_dstBuf->isCanceled())
break;
deSleep(FILEREADER_IDLE_SLEEP);
if (m_dstBuf->isCanceled())
break;
else
continue;
}
else if (err == ERROR_BROKEN_PIPE)
break;
else
throw Error(err, "GetOverlappedResult() failed");
}
if (numBytesRead == 0)
throw Error(GetLastError(), "Reading from file failed");
else
offset += (deUint64)numBytesRead;
}
else if (waitRes == WAIT_OBJECT_0 + 1)
{
// Cancel.
if (!CancelIo(m_handle))
throw Error(GetLastError(), "CancelIo() failed");
break;
}
else
throw Error(GetLastError(), "WaitForMultipleObjects() failed");
try
{
m_dstBuf->write((int)numBytesRead, &tmpBuf[0]);
m_dstBuf->flush();
}
catch (const ThreadedByteBuffer::CanceledException&)
{
// Canceled.
break;
}
}
}
catch (const std::exception& e)
{
// \todo [2013-08-13 pyry] What to do?
printf("win32::FileReader::run(): %s\n", e.what());
}
}
void FileReader::stop (void)
{
if (!isStarted())
return; // Nothing to do.
m_cancelEvent.setSignaled();
// Join thread.
join();
m_cancelEvent.reset();
m_handle = INVALID_HANDLE_VALUE;
}
// TestLogReader
TestLogReader::TestLogReader (void)
: m_logBuffer (LOG_BUFFER_BLOCK_SIZE, LOG_BUFFER_NUM_BLOCKS)
, m_logFile (INVALID_HANDLE_VALUE)
, m_reader (&m_logBuffer)
{
}
TestLogReader::~TestLogReader (void)
{
if (m_logFile != INVALID_HANDLE_VALUE)
CloseHandle(m_logFile);
}
void TestLogReader::start (const char* filename)
{
DE_ASSERT(m_logFile == INVALID_HANDLE_VALUE && !m_reader.isStarted());
m_logFile = CreateFile(filename,
GENERIC_READ,
FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
DE_NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
DE_NULL);
if (m_logFile == INVALID_HANDLE_VALUE)
throw Error(GetLastError(), "Failed to open log file");
m_reader.start(m_logFile);
}
void TestLogReader::stop (void)
{
if (!m_reader.isStarted())
return; // Nothing to do.
m_logBuffer.cancel();
m_reader.stop();
CloseHandle(m_logFile);
m_logFile = INVALID_HANDLE_VALUE;
m_logBuffer.clear();
}
// Process
Process::Process (void)
: m_state (STATE_NOT_STARTED)
, m_exitCode (0)
, m_standardIn (INVALID_HANDLE_VALUE)
, m_standardOut (INVALID_HANDLE_VALUE)
, m_standardErr (INVALID_HANDLE_VALUE)
{
deMemset(&m_procInfo, 0, sizeof(m_procInfo));
}
Process::~Process (void)
{
try
{
if (isRunning())
{
kill();
waitForFinish();
}
}
catch (...)
{
}
cleanupHandles();
}
void Process::cleanupHandles (void)
{
DE_ASSERT(!isRunning());
if (m_standardErr != INVALID_HANDLE_VALUE)
CloseHandle(m_standardErr);
if (m_standardOut != INVALID_HANDLE_VALUE)
CloseHandle(m_standardOut);
if (m_standardIn != INVALID_HANDLE_VALUE)
CloseHandle(m_standardIn);
if (m_procInfo.hProcess)
CloseHandle(m_procInfo.hProcess);
if (m_procInfo.hThread)
CloseHandle(m_procInfo.hThread);
m_standardErr = INVALID_HANDLE_VALUE;
m_standardOut = INVALID_HANDLE_VALUE;
m_standardIn = INVALID_HANDLE_VALUE;
deMemset(&m_procInfo, 0, sizeof(m_procInfo));
}
__declspec(thread) static int t_pipeNdx = 0;
static void createPipeWithOverlappedIO (HANDLE* readHandleOut, HANDLE* writeHandleOut, deUint32 readMode, deUint32 writeMode, SECURITY_ATTRIBUTES* securityAttr)
{
const int defaultBufSize = 4096;
char pipeName[128];
HANDLE readHandle;
HANDLE writeHandle;
DE_ASSERT(((readMode | writeMode) & ~FILE_FLAG_OVERLAPPED) == 0);
deSprintf(pipeName, sizeof(pipeName), "\\\\.\\Pipe\\dEQP-ExecServer-%08x-%08x-%08x",
GetCurrentProcessId(),
GetCurrentThreadId(),
t_pipeNdx++);
readHandle = CreateNamedPipe(pipeName, /* Pipe name. */
PIPE_ACCESS_INBOUND|readMode, /* Open mode. */
PIPE_TYPE_BYTE|PIPE_WAIT, /* Pipe flags. */
1, /* Max number of instances. */
defaultBufSize, /* Output buffer size. */
defaultBufSize, /* Input buffer size. */
0, /* Use default timeout. */
securityAttr);
if (readHandle == INVALID_HANDLE_VALUE)
throw Error(GetLastError(), "CreateNamedPipe() failed");
writeHandle = CreateFile(pipeName,
GENERIC_WRITE, /* Access mode. */
0, /* No sharing. */
securityAttr,
OPEN_EXISTING, /* Assume existing object. */
FILE_ATTRIBUTE_NORMAL|writeMode, /* Open mode / flags. */
DE_NULL /* Template file. */);
if (writeHandle == INVALID_HANDLE_VALUE)
{
DWORD openErr = GetLastError();
CloseHandle(readHandle);
throw Error(openErr, "Failed to open created pipe, CreateFile() failed");
}
*readHandleOut = readHandle;
*writeHandleOut = writeHandle;
}
void Process::start (const char* commandLine, const char* workingDirectory)
{
// Pipes.
HANDLE stdInRead = INVALID_HANDLE_VALUE;
HANDLE stdInWrite = INVALID_HANDLE_VALUE;
HANDLE stdOutRead = INVALID_HANDLE_VALUE;
HANDLE stdOutWrite = INVALID_HANDLE_VALUE;
HANDLE stdErrRead = INVALID_HANDLE_VALUE;
HANDLE stdErrWrite = INVALID_HANDLE_VALUE;
if (m_state == STATE_RUNNING)
throw std::runtime_error("Process already running");
else if (m_state == STATE_FINISHED)
{
// Process finished, clean up old cruft.
cleanupHandles();
m_state = STATE_NOT_STARTED;
}
// Create pipes
try
{
SECURITY_ATTRIBUTES securityAttr;
STARTUPINFO startInfo;
deMemset(&startInfo, 0, sizeof(startInfo));
deMemset(&securityAttr, 0, sizeof(securityAttr));
// Security attributes for inheriting handle.
securityAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
securityAttr.bInheritHandle = TRUE;
securityAttr.lpSecurityDescriptor = DE_NULL;
createPipeWithOverlappedIO(&stdInRead, &stdInWrite, 0, FILE_FLAG_OVERLAPPED, &securityAttr);
createPipeWithOverlappedIO(&stdOutRead, &stdOutWrite, FILE_FLAG_OVERLAPPED, 0, &securityAttr);
createPipeWithOverlappedIO(&stdErrRead, &stdErrWrite, FILE_FLAG_OVERLAPPED, 0, &securityAttr);
if (!SetHandleInformation(stdInWrite, HANDLE_FLAG_INHERIT, 0) ||
!SetHandleInformation(stdOutRead, HANDLE_FLAG_INHERIT, 0) ||
!SetHandleInformation(stdErrRead, HANDLE_FLAG_INHERIT, 0))
throw Error(GetLastError(), "SetHandleInformation() failed");
// Startup info for process.
startInfo.cb = sizeof(startInfo);
startInfo.hStdError = stdErrWrite;
startInfo.hStdOutput = stdOutWrite;
startInfo.hStdInput = stdInRead;
startInfo.dwFlags |= STARTF_USESTDHANDLES;
if (!CreateProcess(DE_NULL, (LPTSTR)commandLine, DE_NULL, DE_NULL, TRUE /* inherit handles */, 0, DE_NULL, workingDirectory, &startInfo, &m_procInfo))
throw Error(GetLastError(), "CreateProcess() failed");
}
catch (...)
{
if (stdInRead != INVALID_HANDLE_VALUE) CloseHandle(stdInRead);
if (stdInWrite != INVALID_HANDLE_VALUE) CloseHandle(stdInWrite);
if (stdOutRead != INVALID_HANDLE_VALUE) CloseHandle(stdOutRead);
if (stdOutWrite != INVALID_HANDLE_VALUE) CloseHandle(stdOutWrite);
if (stdErrRead != INVALID_HANDLE_VALUE) CloseHandle(stdErrRead);
if (stdErrWrite != INVALID_HANDLE_VALUE) CloseHandle(stdErrWrite);
throw;
}
// Store handles to be kept.
m_standardIn = stdInWrite;
m_standardOut = stdOutRead;
m_standardErr = stdErrRead;
// Close other ends of handles.
CloseHandle(stdErrWrite);
CloseHandle(stdOutWrite);
CloseHandle(stdInRead);
m_state = STATE_RUNNING;
}
bool Process::isRunning (void)
{
if (m_state == STATE_RUNNING)
{
int exitCode;
BOOL result = GetExitCodeProcess(m_procInfo.hProcess, (LPDWORD)&exitCode);
if (result != TRUE)
throw Error(GetLastError(), "GetExitCodeProcess() failed");
if (exitCode == STILL_ACTIVE)
return true;
else
{
// Done.
m_exitCode = exitCode;
m_state = STATE_FINISHED;
return false;
}
}
else
return false;
}
void Process::waitForFinish (void)
{
if (m_state == STATE_RUNNING)
{
if (WaitForSingleObject(m_procInfo.hProcess, INFINITE) != WAIT_OBJECT_0)
throw Error(GetLastError(), "Waiting for process failed, WaitForSingleObject() failed");
if (isRunning())
throw std::runtime_error("Process is still alive");
}
else
throw std::runtime_error("Process is not running");
}
void Process::stopProcess (bool kill)
{
if (m_state == STATE_RUNNING)
{
if (!TerminateProcess(m_procInfo.hProcess, kill ? -1 : 0))
throw Error(GetLastError(), "TerminateProcess() failed");
}
else
throw std::runtime_error("Process is not running");
}
void Process::terminate (void)
{
stopProcess(false);
}
void Process::kill (void)
{
stopProcess(true);
}
} // win32
Win32TestProcess::Win32TestProcess (void)
: m_process (DE_NULL)
, m_processStartTime (0)
, m_infoBuffer (INFO_BUFFER_BLOCK_SIZE, INFO_BUFFER_NUM_BLOCKS)
, m_stdOutReader (&m_infoBuffer)
, m_stdErrReader (&m_infoBuffer)
{
}
Win32TestProcess::~Win32TestProcess (void)
{
delete m_process;
}
void Win32TestProcess::start (const char* name, const char* params, const char* workingDir, const char* caseList)
{
bool hasCaseList = strlen(caseList) > 0;
XS_CHECK(!m_process);
de::FilePath logFilePath = de::FilePath::join(workingDir, "TestResults.qpa");
m_logFileName = logFilePath.getPath();
// Remove old file if such exists.
// \note Sometimes on Windows the test process dies slowly and may not release handle to log file
// until a bit later.
// \todo [2013-07-15 pyry] This should be solved by improving deProcess and killing all child processes as well.
{
int tryNdx = 0;
while (tryNdx < MAX_OLD_LOGFILE_DELETE_ATTEMPTS && deFileExists(m_logFileName.c_str()))
{
if (deDeleteFile(m_logFileName.c_str()))
break;
deSleep(LOGFILE_DELETE_SLEEP_MS);
tryNdx += 1;
}
if (deFileExists(m_logFileName.c_str()))
throw TestProcessException(string("Failed to remove '") + m_logFileName + "'");
}
// Construct command line.
string cmdLine = de::FilePath(name).isAbsolutePath() ? name : de::FilePath::join(workingDir, name).normalize().getPath();
cmdLine += string(" --deqp-log-filename=") + logFilePath.getBaseName();
if (hasCaseList)
cmdLine += " --deqp-stdin-caselist";
if (strlen(params) > 0)
cmdLine += string(" ") + params;
DE_ASSERT(!m_process);
m_process = new win32::Process();
try
{
m_process->start(cmdLine.c_str(), strlen(workingDir) > 0 ? workingDir : DE_NULL);
}
catch (const std::exception& e)
{
delete m_process;
m_process = DE_NULL;
throw TestProcessException(e.what());
}
m_processStartTime = deGetMicroseconds();
// Create stdout & stderr readers.
m_stdOutReader.start(m_process->getStdOut());
m_stdErrReader.start(m_process->getStdErr());
// Start case list writer.
if (hasCaseList)
m_caseListWriter.start(caseList, m_process->getStdIn());
}
void Win32TestProcess::terminate (void)
{
if (m_process)
{
try
{
m_process->kill();
}
catch (const std::exception& e)
{
printf("Win32TestProcess::terminate(): Failed to kill process: %s\n", e.what());
}
}
}
void Win32TestProcess::cleanup (void)
{
m_caseListWriter.stop();
// \note Buffers must be canceled before stopping readers.
m_infoBuffer.cancel();
m_stdErrReader.stop();
m_stdOutReader.stop();
m_testLogReader.stop();
// Reset buffers.
m_infoBuffer.clear();
if (m_process)
{
try
{
if (m_process->isRunning())
{
m_process->kill();
m_process->waitForFinish();
}
}
catch (const std::exception& e)
{
printf("Win32TestProcess::cleanup(): Failed to kill process: %s\n", e.what());
}
delete m_process;
m_process = DE_NULL;
}
}
int Win32TestProcess::readTestLog (deUint8* dst, int numBytes)
{
if (!m_testLogReader.isRunning())
{
if (deGetMicroseconds() - m_processStartTime > LOG_FILE_TIMEOUT*1000)
{
// Timeout, kill process.
terminate();
return 0; // \todo [2013-08-13 pyry] Throw exception?
}
if (!deFileExists(m_logFileName.c_str()))
return 0;
// Start reader.
m_testLogReader.start(m_logFileName.c_str());
}
DE_ASSERT(m_testLogReader.isRunning());
return m_testLogReader.read(dst, numBytes);
}
bool Win32TestProcess::isRunning (void)
{
if (m_process)
return m_process->isRunning();
else
return false;
}
int Win32TestProcess::getExitCode (void) const
{
if (m_process)
return m_process->getExitCode();
else
return -1;
}
} // xs