/*------------------------------------------------------------------------- * 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 #include 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 tmpBuf(FILEREADER_TMP_BUFFER_SIZE); uint64_t 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 += (uint64_t)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, uint32_t readMode, uint32_t 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); } } // namespace 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(uint8_t *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; } } // namespace xs