From: <sv...@va...> - 2010-03-03 22:34:38
|
Author: cerion Date: 2010-03-03 22:14:50 +0000 (Wed, 03 Mar 2010) New Revision: 479 Log: Major rework of process control - start/stop valgrind process, reading the generated xml log asyncronously. - process control and log parsing now independent: except for error situations, when each may stop the other by callng the main 'stopProcess() function. - should actually work now, and not just the happy flow. Just need time to write all the unit tests! Fixed a couple of small memory leaks Still TODO's from memcheck_object.cpp: //TODO: mock a valgrind process, and setup some unit tests (and a test framework!) //TODO: have popups called from toolview, not object... maybe. //TODO: make sure not too many popup vkError()'s for any particular state flow. //TODO: ditto for statusMsg()'s //TODO: make sure statusMsg()s always correct with current state. Modified: branches/valkyrie_qt4port/mainwindow.cpp branches/valkyrie_qt4port/objects/memcheck_object.cpp branches/valkyrie_qt4port/objects/memcheck_object.h branches/valkyrie_qt4port/objects/valgrind_object.cpp branches/valkyrie_qt4port/objects/valkyrie_object.cpp branches/valkyrie_qt4port/utils/vglogreader.cpp branches/valkyrie_qt4port/utils/vglogreader.h branches/valkyrie_qt4port/utils/vk_logpoller.cpp branches/valkyrie_qt4port/utils/vk_logpoller.h branches/valkyrie_qt4port/utils/vk_utils.cpp branches/valkyrie_qt4port/utils/vk_utils.h Modified: branches/valkyrie_qt4port/mainwindow.cpp =================================================================== --- branches/valkyrie_qt4port/mainwindow.cpp 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/mainwindow.cpp 2010-03-03 22:14:50 UTC (rev 479) @@ -872,7 +872,10 @@ #endif if ( !valkyrie->runTool( toolViewStack->currentToolId(), procId ) ) { - vkError( this, "Run Tool", "Failed to complete execution." ); + //TODO: make sure all fail cases have given a message to the user already + + VK_DEBUG( "Failed to complete execution for toolId (%d), procId (%d)", + toolViewStack->currentToolId(), procId ); } } @@ -894,7 +897,7 @@ if ( vkConfig->value( "valkyrie/binary").toString().isEmpty() ) { vkInfo( this, "Run Valgrind: No program specified", - "Please specify (via Edit::Options::Binary) the path to the program<br>" + "Please specify (via Options->Valkyrie->Binary) the path to the program<br>" "you wish to run, along with any arguments required"); openOptions(); @@ -912,6 +915,7 @@ void MainWindow::stopTool() { cerr << "TODO: MainWindow::stopTool()" << endl; + valkyrie->stopTool( toolViewStack->currentToolId() ); } Modified: branches/valkyrie_qt4port/objects/memcheck_object.cpp =================================================================== --- branches/valkyrie_qt4port/objects/memcheck_object.cpp 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/objects/memcheck_object.cpp 2010-03-03 22:14:50 UTC (rev 479) @@ -33,11 +33,24 @@ #include <QDir> #include <QTimer> +// Waiting for Vg to start: +#define WAIT_VG_START_MAX 1000 // msecs before giving up +#define WAIT_VG_START_SLEEP 100 // msecs to sleep each loop +#define WAIT_VG_START_LOOPS (WAIT_VG_START_MAX / WAIT_VG_START_SLEEP) -#define TIMEOUT_STOPPROC 2000 // msec until stop process -#define TIMEOUT_KILLPROC 2000 // msec until kill process (after tried to stop) +// Waiting for Vg to die: +#define TIMEOUT_KILL_PROC 2000 // msec: 'please stop?' to 'die!' +#define TIMEOUT_WAIT_UNTIL_DONE 5000 // msec: 'half done' to 'advise stop' +//TODO: mock a valgrind process, and setup some unit tests (and a test framework!) +//TODO: have popups called from toolview, not object... maybe. +//TODO: make sure not too many popup vkError()'s for any particular state flow. +//TODO: ditto for statusMsg()'s +//TODO: make sure statusMsg()s always correct with current state. + + + /*! class Memcheck */ @@ -49,6 +62,7 @@ vgproc = 0; vgreader = 0; logpoller = new VkLogPoller( this, "memcheck logpoller" ); + connect( logpoller, SIGNAL(logUpdated()), this, SLOT(readVgLog()) ); @@ -58,7 +72,10 @@ Memcheck::~Memcheck() { + setProcessId( VGTOOL::PROC_NONE ); + if (vgproc) { +// TODO: does this work? vgproc->disconnect(); // so no signal calling processDone() if ( vgproc->state() != QProcess::NotRunning ) { vgproc->terminate(); @@ -67,12 +84,13 @@ delete vgproc; vgproc = 0; } + if (vgreader) { delete vgreader; vgreader = 0; } - // logpoller deleted by it's parent: 'this' + // logpoller auto deleted by Qt when 'this' dies // unsaved log... delete our temp file if (!fileSaved && !saveFname.isEmpty()) @@ -192,7 +210,7 @@ /* check argval for this option, updating if necessary. called by parseCmdArgs() and gui option pages -------------------- */ -int Memcheck::checkOptArg( int optid, QString& argval ) +int Memcheck::checkOptArg( int optid, QString& )//argval ) { vk_assert( optid >= 0 && optid < MEMCHECK::NUM_OPTS ); @@ -245,16 +263,14 @@ connect( this, SIGNAL(running(bool)), toolView, SLOT(setState(bool)) ); - setProcessId( VGTOOL::PROC_NONE ); - return toolView; } /* outputs a message to the status bar. */ -void Memcheck::statusMsg( QString hdr, QString msg ) +void Memcheck::statusMsg( QString msg ) { - emit message( hdr + ": " + msg ); + emit message( "Memcheck: " + msg ); } @@ -273,7 +289,7 @@ "<p>Do you want to abort it ?</p>" ); /* Note: process may have finished while waiting for user */ if ( ok == MsgBox::vkYes ) { - stop(); /* abort */ + stopProcess(); /* abort */ vk_assert( !isRunning() ); } else if ( ok == MsgBox::vkNo ) { return false; /* continue */ @@ -323,9 +339,13 @@ } +/*! + Start a memcheck process + - Slot, called from the Memcheck ToolView +*/ bool Memcheck::start( int procId, QStringList vgflags ) { -cerr << "Memcheck::start(): " << procId << endl; +//cerr << "Memcheck::start(): " << procId << endl; vk_assert( procId > VGTOOL::PROC_NONE ); vk_assert( procId < VGTOOL::PROC_MAX ); @@ -339,8 +359,6 @@ case MEMCHECK::PROC_PARSE_LOG: ok = parseLogFile(); break; -//TODO: maybe someday. -// case merge: ok = mergeLogFiles(); break; default: vk_assert_never_reached(); } @@ -350,78 +368,33 @@ /*! - Stop a process. - Try to be nice, but in case doesn't work, setup timer to guarantee it dies. -//TODO: Perhaps the view want's to hang around and wait until process is dead. but that's up to the view. + Stop a memcheck process + - Slot, called from the Memcheck ToolView + + Note: could take a while (TIMEOUT_KILL_PROC) before we return + - but we do call qApp->processEvents() */ void Memcheck::stop() { - if ( !isRunning() ) - return; +//cerr << "Memcheck::stop() " << endl; - // whatever else we do, stop trying to read from the log. - if ( logpoller ) - logpoller->stop(); - - switch ( getProcessId() ) { - case VGTOOL::PROC_VALGRIND: { - if ( vgproc && ( vgproc->state() != QProcess::NotRunning ) ) { - vkPrint("Memcheck::stop(): process starting/running: terminate."); - - vgproc->terminate(); // if & when succeeds: signal -> processDone() - - // in case doesn't want to stop, start timer to really kill it off. - QTimer::singleShot( TIMEOUT_KILLPROC, this, SLOT( killProcess() ) ); - } - else { - vkPrint("Memcheck::stop(): process was already stopped."); - } - } break; - - case MEMCHECK::PROC_PARSE_LOG: { // parse log - // TODO - VK_DEBUG("TODO: Memcheck::stop(parse log)" ); - } break; - -#if 0 - case TODO: { // merge logs - if (vgproc && vgproc->isRunning() ) - vgproc->stop(); // signal -> processDone() - } break; -#endif - default: - vk_assert_never_reached(); - } - - return; + // could take a while (TIMEOUT_KILL_PROC) before really done, + stopProcess(); + vk_assert( vgproc == 0 ); + vk_assert( !this->isRunning() ); } /*! - Kills a process immediately - - only called as a result of kill timer timeout -*/ -void Memcheck::killProcess() -{ - if ( vgproc && ( vgproc->state() != QProcess::NotRunning ) ) { -vkPrint("Memcheck::killProcess(): kill the process"); - vgproc->kill(); // process will die, then signal -> processDone() - } - else { -vkPrint("Memcheck::killProcess(): process already stopped."); - } -} - - -/*! If --vg-opt=<arg> was specified on the cmd-line, called by valkyrie->runTool(); if set via the run-button in the gui, then MainWindow::run() calls valkyrie->runTool(). - Auto-generated logs always saved in hard-coded (configured) log dir + Auto-generated logs always saved in a (configured) temp log dir */ bool Memcheck::runValgrind( QStringList vgflags ) { + VK_DEBUG("Start Vg run"); saveFname = vk_mkstemp( QString( VkConfig::vkTmpDir() ) + "mc_log", "xml" ); vk_assert( !saveFname.isEmpty() ); @@ -430,14 +403,19 @@ setProcessId( VGTOOL::PROC_VALGRIND ); fileSaved = false; - statusMsg( "Memcheck", "Running ... " ); + statusMsg( "Starting Valgrind ..." ); bool ok = startProcess( vgflags ); if (!ok) { - statusMsg( "Memcheck", "Failed" ); + VK_DEBUG("Error: Failed to start Valgrind"); + vk_assert( this->getProcessId() == VGTOOL::PROC_NONE ); + statusMsg( "Error: Failed to start Valgrind" ); fileSaved = true; - setProcessId( VGTOOL::PROC_NONE ); + } else { + VK_DEBUG("Started Valgrind"); + vk_assert( this->getProcessId() == VGTOOL::PROC_VALGRIND ); + statusMsg( "Started Valgrind ..." ); } return ok; @@ -457,7 +435,7 @@ //TODO: pass this via flags from the toolview, or something. VkOption* opt = options.getOption( VALKYRIE::VIEW_LOG ); QString log_file = vkConfig->value( opt->configKey() ).toString(); - statusMsg( "Parsing", log_file ); + statusMsg( "Parsing '" + log_file + "'" ); //TODO: file checks #if 0 @@ -483,101 +461,52 @@ // Parse the log VgLogReader vgLogFileReader( getToolView()->vgLogPtr() ); bool success = vgLogFileReader.parse( log_file ); - if (!success) { + + if (success) { + statusMsg( "Loaded Logfile '" + log_file + "'" ); + saveFname = log_file; + } else { + statusMsg( "Error Parsing Logfile '" + log_file + "'" ); + VgLogHandler* hnd = vgLogFileReader.handler(); - statusMsg( "Parsing", "Error" ); vkError( getToolView(), "XML Parse Error", "<p>%s</p>", qPrintable( escapeEntities(hnd->fatalMsg()) ) ); } - if (success) { - saveFname = log_file; - statusMsg( "Loaded", log_file ); - } else { - statusMsg( "Parse failed", log_file ); - } setProcessId( VGTOOL::PROC_NONE ); - return success; } -/* if --merge=<file_list> was specified on the cmd-line, called by - valkyrie->runTool(); if set via the open-file-dialog in the gui, - called by MemcheckView::openMergeFile(). either way, the value in - [valkyrie:merge] is what we need to know - - Auto-generated logs always saved in hard-coded (configured) log dir +/*! + Run a VKProcess, as given by 'flags'. + - Reads ouput from file, loading this to the listview. */ -bool Memcheck::mergeLogFiles() +bool Memcheck::startProcess( QStringList flags ) { -//TODO. maybe. -#if 0 - QString fname_logList = vkConfig->rdEntry( "merge", "valkyrie" ); - statusMsg( "Merging logs in file-list", fname_logList ); - - saveFname = vk_mkstemp( QString( VkConfig::vkTmpDir() ) - + "mc_merged", "xml" ); - vk_assert( !saveFname.isEmpty() ); + VK_DEBUG("Start VgProcess"); + vk_assert( getToolView() != 0 ); - QStringList flags; - flags << vkConfig->rdEntry( "merge-exec","valkyrie"); - flags << "-f"; - flags << fname_logList; - flags << "-o"; - flags << saveFname; + QString program = flags.at( 0 ); + QStringList args = flags.mid(1); - setProcessId( VkRunState::TOOL2 ); - fileSaved = false; - statusMsg( "Merge Logs", "Running ... " ); - - bool ok = startProcess( flags ); - - if (!ok) { - statusMsg( "Merge Logs", "Failed" ); - fileSaved = true; - setProcessId( VkRunState::STOPPED ); - } - return ok; -#endif - return true; -} - - -/* Run a VKProcess, as given by 'flags'. - Reads ouput from file, loading this to the listview. -*/ -bool Memcheck::startProcess( QStringList flags ) -{ - QString program = flags.at( 0 ); - QStringList args = flags.mid(1); - - // vkPrint("Memcheck::startProcess()"); +#ifdef DEBUG_ON for ( int i=0; i<flags.count(); i++ ) vkPrint("flag[%d] --> %s", i, qPrintable( flags[i] ) ); - vk_assert( getToolView() != 0 ); +#endif // new vgreader - view may have been recreated, so need up-to-date ptr vk_assert( vgreader == 0 ); vgreader = new VgLogReader( getToolView()->vgLogPtr() ); - // start the log parse - nothing written yet tho. - if (!vgreader->parse( saveFname, true )) { - QString errMsg = vgreader->handler()->fatalMsg(); - VK_DEBUG("vgreader failed to start parsing empty log\n"); - vkError( getToolView(), "Process Startup Error", - "<p>Failed to start XML parser:<br>%s</p>", - qPrintable( errMsg ) ); - goto failed_startup; - } - - // start a new process, listening on exit signal + // start a new process, listening on exit signal to call processDone(). + // - once Vg is done, we can read the remainder of the log in one last go. vk_assert( vgproc == 0 ); vgproc = new QProcess( this->toolView ); connect( vgproc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processDone(int, QProcess::ExitStatus)) ); - // forward vg process stdout/err to our stdout/err respectively + // forward vgproc stdout/err to our stdout/err respectively vgproc->setProcessChannelMode( QProcess::ForwardedChannels ); // set working directory @@ -585,193 +514,375 @@ // start running process vgproc->start( program, args ); + VK_DEBUG("Started VgProcess"); -//TODO: call vgproc->waitForStarted(), or sthgn with a timeout, and then error to user... -#if 0 - VK_DEBUG("process failed to start"); + // Make sure Vg started ok before moving further. + // Don't bother using QProcess::start(): + // 1) Vg may have finished already(!) + // 2) QXmlSimpleReader won't start on an empty log: seems to need at least "<?x" + // So just wait for a while until we find the valgrind output log... + int i=0; + while ( i < WAIT_VG_START_LOOPS) { + if ( QFile::exists( saveFname ) ) + break; + usleep( WAIT_VG_START_SLEEP * 1000 ); + i++; + } + + // If no logfile found, we have a problem: + if ( i >= WAIT_VG_START_LOOPS ) { + VK_DEBUG("Valgrind failed to start properly (process not running)"); vkError( getToolView(), "Process Startup Error", - "<p>Failed to start process:<br>%s<br><br>" - "Please verify the path to Valgrind in Options::Valkyrie.</p>", - qPrintable( flags.join(" ") ) ); + "<p>Failed to start valgrind properly.<br>" + "Please verify Valgrind and Binary paths (via Options->Valkyrie).<br>" + "Try running Valgrind (with _exactly_ the same arguments) via the command-line" + "<br><br>%s", + qPrintable( flags.join("<br> ") ) ); goto failed_startup; } -#endif - // poll log for latest data - logpoller->start(); + // poll log regularly to trigger parsing of the latest data via readVgLog() + logpoller->start( 250 ); // msec -vkPrint(" - END MC::startProcess()" ); + statusMsg( "Error Parsing Logfile '" + saveFname + "'" ); + + // doesn't matter if processDone() or readVgLog() gets called first. + VK_DEBUG("Started logpoller"); return true; - failed_startup: +failed_startup: VK_DEBUG("failed_startup: '%s'", qPrintable( flags.join(" ") )); - if (logpoller != 0) { + stopProcess(); + return false; +} + + +/*! + Stop a process. + Try to be nice, but if nice don't get the job done, hire a + one off timer to singleShoot the bugger. + + Note: We may wait around in this function for Vg to die. + - but we do call qApp->processEvents() +*/ +void Memcheck::stopProcess() +{ + // dont try to stop a stopped process. + if ( !isRunning() ) + return; + + VK_DEBUG("Stopping VgProcess"); + statusMsg( "Stopping Valgrind process ..." ); + + // first things first: stop trying to read from the log. + if ( logpoller != 0 && logpoller->isActive() ) { logpoller->stop(); } - if (vgreader != 0) { + if ( vgreader != 0 ) { delete vgreader; vgreader = 0; } - if (vgproc != 0) { - delete vgproc; - vgproc = 0; - } - return false; + + switch ( getProcessId() ) { + case VGTOOL::PROC_VALGRIND: { + // if vgproc is alive, shut it down + if ( vgproc && ( vgproc->state() != QProcess::NotRunning ) ) { + VK_DEBUG("VgProcess starting/running: Terminate."); + vkPrint("Memcheck::stopProcess(): process starting/running: terminate."); + + vgproc->terminate(); // if & when succeeds: signal -> processDone() + + // in case doesn't want to stop, start timer to really kill it off. + QTimer::singleShot( TIMEOUT_KILL_PROC, this, SLOT( killProcess() ) ); + + // Now it's a race between killProcess() and processDone() + // either way, hang around until vgproc is cleaned up. + + VK_DEBUG("Waiting for VgProcess to die."); + //TODO: some bad joke re waiting for the inheritance. + + int sleepDuration = 100; // msec + int nCycles = (TIMEOUT_KILL_PROC / sleepDuration ) + 1;// +1: ensure Vg gone + for ( int i=0; i<nCycles; i++ ) { + qApp->processEvents( QEventLoop::AllEvents, 10/*max msecs*/ ); + usleep( 1000 * sleepDuration ); + if ( vgproc == 0 ) + break; + } + // all should now be well. + vk_assert( vgproc == 0 ); + vk_assert( !this->isRunning() ); + } + else { + VK_DEBUG("VgProcess already stopped (or never started)."); + if ( vgproc ) { + delete vgproc; + vgproc = 0; + } + setProcessId( VGTOOL::PROC_NONE ); + } + } break; + + case MEMCHECK::PROC_PARSE_LOG: { // parse log + // TODO + VK_DEBUG("TODO: Memcheck::stop(parse log)" ); + setProcessId( VGTOOL::PROC_NONE ); + } break; + + default: + vk_assert_never_reached(); + } } /*! - Process exited: - - self / external signal / user via 'stop()' / - - terminated from readVgLog because of an xml parse error - Stops logfile polling, checks xml parsing for errors, - checks exitstatus, cleans up. + Kills a process immediately + - only called as a result of kill timer timeout from stopProcess() + + If process already stopped, cleans it up. Else kills it, which + signals processDone(), which cleans it up. */ -void Memcheck::processDone( int exitCode, QProcess::ExitStatus exitStatus ) +void Memcheck::killProcess() { - cerr << "Memcheck::processDone( " << exitCode << ", " << exitStatus << " )" << endl; + if ( vgproc == 0 ) { + // vgproc died and called processDone() already + VK_DEBUG("VgProcess already cleaned up."); + return; + } - // vkPrint("Memcheck::processDone()"); - vk_assert( vgproc != 0 ); - vk_assert( vgreader != 0 ); - vk_assert( logpoller != 0 ); - bool runError = false; + if ( vgproc->state() == QProcess::NotRunning ) { + VK_DEBUG("VgProcess already stopped."); - // stop polling logfile ------------------------------------------ - logpoller->stop(); + // cleanup already. + delete vgproc; + vgproc = 0; + setProcessId( VGTOOL::PROC_NONE ); - // deal with log reader ------------------------------------------ - // if not finished && no error, try reading log data one last time - if (!vgreader->handler()->finished() && - vgreader->handler()->fatalMsg().isEmpty()) - readVgLog(); + } else { + VK_DEBUG("VgProcess still running: kill it!"); + vgproc->kill(); + // process will die, signalling processDone(), + // which will cleanup vgproc. + } +} - // did log parsing go ok? - QString fatalMsg = vgreader->handler()->fatalMsg(); - if ( !fatalMsg.isEmpty() ) { - // fatal log error... - runError = true; - // vkPrint(" - Memcheck::processDone(): fatal error"); - if (getProcessId() == VGTOOL::PROC_VALGRIND) { - statusMsg( "Memcheck", "Error parsing output log" ); - vkError( getToolView(), "XML Parse Error", - "<p>Error parsing Valgrind XML output:<br>%s</p>", - qPrintable( str2html( fatalMsg ) ) ); - } else { - vk_assert_never_reached(); -//TODO. maybe -#if 0 - statusMsg( "Merge Logs", "Error parsing output log" ); - vkError( getToolView(), "Parse Error", - "<p>Error parsing output log</p>" ); -#endif - } - } else if ( !vgreader->handler()->finished() ) { - /* no fatal error, but STILL not reached end of log, either: - - valgrind xml output not completed properly - - merge failed */ - runError = true; - // vkPrint(" - Memcheck::processDone(): parsing STILL not finished"); +/*! + Process exited, from one of: + - process finished/died/killed external to valkyrie + - startup failed, and called stopProcess() + - user got bored and called stopProcess() - if (getProcessId() == VGTOOL::PROC_VALGRIND) { - statusMsg( "Memcheck", "Error - incomplete output log" ); - vkError( getToolView(), "XML Parse Error", - "<p>Valgrind XML output is incomplete</p>" ); - } else { - vk_assert_never_reached(); -//TODO. maybe. -#if 0 - statusMsg( "Merge Logs", "Error - incomplete output log" ); - vkError( getToolView(), "Parse Error", - "<p>Failed to parse merge result</p>" ); -#endif - } - } + - stopProcess() called terminate(), which called this directly + - stopProcess() started a timer to call killProcess(), to call kill(), + which called this directly. + Since both processDone() and killProcess() must clean up vgproc, + be careful to check who's cleaned up already. - // check process exit status - valgrind might have bombed - if ( exitStatus == QProcess::NormalExit && exitCode == 0 ) { - // vkPrint(" - Memcheck::processDone(): process exited ok"); - } else { - // vkPrint(" - Memcheck::processDone(): process failed (%d)", exitStatus); - if (getProcessId() == VGTOOL::PROC_VALGRIND) { - vkError( getToolView(), "Run Error", - "<p>Process %s, with return value %d.<br> \ - This is likely to simply be the client program \ - return value. If, however, you suspect Valgrind \ - itself may have crashed, please click 'OK', then \ - 'Save Log' and examine for details.</p>", - (exitStatus == QProcess::NormalExit) ? "exited normally" : "crashed", - exitCode ); - } else { - vk_assert_never_reached(); -//TODO: maybe. -#if 0 - vkError( getToolView(), "Parse Error", - "<p>Merge process exited with return value %d.<br> \ - Please check the terminal for error messages.</p>", - exitStatus); -#endif - } + Don't worry about the parser state: just deal with the Vg process. + - unless Vg quit with an error & parser is still runnning, + in which case, stop the parser too, via stopProcess() +*/ +void Memcheck::processDone( int exitCode, QProcess::ExitStatus exitStatus ) +{ + cerr << "Memcheck::processDone( " << exitCode << ", " << exitStatus << " )" << endl; + vk_assert( getToolView() != 0 ); + if ( vgproc == 0 ) { + // Tiny chance that the timeout called killProcess(), cleaning up vgproc, + // but vgproc already sent an (buffered) call to processDone() directly? + // Don't know if that's even possible with Qt signal/slots, but just in case. + VK_DEBUG("VgProcess already cleaned up."); + return; } - // cleanup ------------------------------------------------------- - delete vgreader; - vgreader = 0; + // cleanup first ------------------------------------------------- delete vgproc; vgproc = 0; + // --------------------------------------------------------------- + // check process exit status - valgrind might have bombed + bool ok = true; + if ( exitStatus == QProcess::NormalExit && exitCode == 0 ) { + VK_DEBUG("Clean VgProcess stop: process exited ok"); + statusMsg( "Finished Valgrind process!" ); - // we're done. --------------------------------------------------- - if (!runError) { // (else we've already set an status error message) - if (getProcessId() == VGTOOL::PROC_VALGRIND) { - statusMsg( "Memcheck", "Finished" ); - } else { - vk_assert_never_reached(); -//TODO: maybe -// statusMsg( "Merge Logs", "Finished" ); + } else { + ok = false; + VK_DEBUG("Error running VgProcess: process failed (%d)", exitStatus); + statusMsg( "Error running Valgrind process" ); + + // Note: when calling a QDialog, qApp still processes events, + // so killProcess() may get called here. + vkError( getToolView(), "Run Error", + "<p>Valgrind process %s, giving return value %d.<br><br>" + "Most likely, you either pressed 'Stop', or the (client)<br>" + "process running under Valgrind died.<br><br>" + "If, however, you suspect Valgrind itself may have crashed,<br>" + "please click 'OK', 'Save Log' and examine for details.</p>", + (exitStatus == QProcess::NormalExit) ? "exited normally" : + "crashed or was killed", + exitCode ); + } + + // if log reader not active anymore, we're done + if ( vgreader == 0 ) { + VK_DEBUG("All done."); + statusMsg( "Finished running Valgrind successfully!" ); + setProcessId( VGTOOL::PROC_NONE ); + } else { + // For a number of reasons, vgreader may continue on a while after + // vgproc has gone (e.g. Vg dies, leaving incomplete xml) + if ( !ok ) { + // process error: stop reader now. + VK_DEBUG("VgProcess finished with error: stop VgReader"); + stopProcess(); } + else { + // Vg finished happily. + // In principle, want pargser also to stop when it's happy, + // but "just in case" Vg doesn't output complete XML (surely not!) + // or the parser is taking too long with too much data, + // wait a while, and, if same state, inform the user what's going on. + + VK_DEBUG("VgProcess finished happy: take another look at " + "VgReader after timeout (%d)", TIMEOUT_WAIT_UNTIL_DONE ); + QTimer::singleShot( TIMEOUT_WAIT_UNTIL_DONE, + this, SLOT( checkParserFinished() ) ); + } } - - setProcessId( VGTOOL::PROC_NONE ); - // vkPrint("Memcheck::processDone(): DONE.\n"); } -/* Read memcheck / logmerge xml output - Called by - - logpoller signals - - processDone() if one last data read needed. + +/*! + Read memcheck / logmerge xml output + - Called by logpoller signals only. + + Don't worry about Valgrind process state: just read the log. + - unless we have a parser error & valgrind is still runnning, + in which case, stop the process too, via stopProcess(). */ void Memcheck::readVgLog() { -vkPrint("Memcheck::readVgLog()"); vk_assert( getToolView() != 0 ); vk_assert( vgreader != 0 ); - vk_assert( vgproc != 0 ); + vk_assert( logpoller != 0 ); + vk_assert( !saveFname.isEmpty() ); - qApp->processEvents(); + // Note: not calling qApp->processEvents(), since parser only + // reads in a limited amount in one go anyway. - // starting or dead -> get out of here. - if ( vgproc->state() != QProcess::Running ) - return; + // try parsing vg xml log ---------------------------------------- + bool ok = true; + QString errHeader; + statusMsg( "Parsing Valgrind XML log..." ); - // Try reading some more data. By failure, stop process. - if ( vgreader && !vgreader->parseContinue()) { -vkPrint("Memcheck::readVgLog(): Parsing Failed. Stopping process after timeout"); + if ( !vgreader->handler()->started() ) { + // first time around... + VK_DEBUG("Start parsing Valgrind XML log"); - // stop trying to read from the log. + ok = vgreader->parse( saveFname, true/*incremental*/ ); + if ( !ok ) { + VK_DEBUG("Error: parse() failed" ); + errHeader = "XML Parse-Startup Error"; + } + } + else { + // we've started, so we'll continue... + VK_DEBUG("Continue parsing Valgrind XML log"); + + ok = vgreader->parseContinue(); + if ( !ok ) { + VK_DEBUG("Error: parseContinue() failed" ); + errHeader = "XML Parse-Continue Error"; + } + } + if ( !vgreader->handler()->fatalMsg().isEmpty() ) { + ok = false; + } + if ( vgreader->handler()->finished() ) { + VK_DEBUG("Reached end of XML log" ); + } + + + // deal with failures -------------------------------------------- + if ( !ok ) { + // parsing failed :-( + VK_DEBUG("Error parsing Valgrind log"); + statusMsg( "Error parsing Valgrind log" ); + + // Failed: print error & stop everything. + QString errMsg = vgreader->handler()->fatalMsg(); + vkError( getToolView(), errHeader, + "<p>Failed to parse Valgrind XML output:<br>%s</p>", + qPrintable( str2html(errMsg) ) ); + } + + // cleanup ------------------------------------------------------- + // if parsing failed, or this was a last call, then cleanup + if ( !ok || vgreader->handler()->finished() ) { + VK_DEBUG("Cleaning up logpoller & reader"); + vk_assert( vgreader != 0 ); + vk_assert( logpoller != 0 ); + vk_assert( logpoller->isActive() ); + + // cleanup. logpoller->stop(); + delete vgreader; + vgreader = 0; -//TODO: necessary? - // give process a little time to finishe before we stop it. - QTimer::singleShot( TIMEOUT_STOPPROC, this, SLOT( stop() ) ); + // if vgproc not active anymore, we're done! + if ( vgproc == 0 ) { + VK_DEBUG("All done."); + statusMsg( "Finished running Valgrind successfully!" ); + setProcessId( VGTOOL::PROC_NONE ); + } + else { + // vgproc is still alive... + if ( !ok ) { + // parse error: stop vgproc now + VK_DEBUG("VgReader finished with error: stop VgProcess"); + stopProcess(); + } + // else: parser finished happily. Allow Vg to stop when it's also happy. + // TODO: Any reason why Vg might need stopping from this state? + // - if any good reason, then dup checkParserFinished() functionality. + } } } /*! + If for some reason Valgrind has finished and the parser still hasn't, + inform the user and remind of option to stopping by hand. + + Notes: + * vgreader->parse() and parseContinue() call QXmlInputSource::fetchData(), + which reads in only a limited amount (512B for Qt3.3.6) from the logfile. + Valgrind, after finishing up, can write a whole bunch of data in one go + to the logfile, which takes some iterations of parserContinue() to read in. + * If Valgrind doesn't write a complete XMLfile (!), this would leave the + parser with incomplete XML, trying to parse it indefinitely. +*/ +void Memcheck::checkParserFinished() +{ + if ( vgproc == 0 && vgreader != 0 ) { + VK_DEBUG("Timeout waiting for parser to finish: Parser _still_ alive."); + vkInfo( getToolView(), "Valgrind finished, but log-reader alive", + "<p>The Valgrind process finished some time ago,<br>" + "but the log reader is still active.<br><br>" + "This could indicate that Valgrind didn't finish<br>" + "writing the XML log properly, or just that" + "there is a huge amount of XML to read.<br><br>" + "Press Stop if you want to force the process to stop.</p>" ); + } else { + VK_DEBUG("Timeout waiting for parser to finish: Parser finished."); + } +} + + +/*! Brings up a fileSaveDialog until successfully saved, or user pressed Cancel. If fname.isEmpty, ask user for a name first. @@ -815,7 +926,7 @@ /* Save to file - we already have everything in saveFname logfile, so just copy that */ -bool Memcheck::saveParsedOutput( QString& fname ) +bool Memcheck::saveParsedOutput( QString& )//fname ) { #if 0 //vkPrint("saveParsedOutput(%s)", fname.latin1() ); Modified: branches/valkyrie_qt4port/objects/memcheck_object.h =================================================================== --- branches/valkyrie_qt4port/objects/memcheck_object.h 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/objects/memcheck_object.h 2010-03-03 22:14:50 UTC (rev 479) @@ -89,7 +89,7 @@ // overriding to avoid casting everywhere MemcheckView* getToolView() { return (MemcheckView*)toolView; } - void statusMsg( QString hdr, QString msg ); + void statusMsg( QString msg ); bool queryFileSave(); bool saveParsedOutput( QString& fname ); @@ -101,7 +101,9 @@ private slots: void processDone( int exitCode, QProcess::ExitStatus exitStatus ); void readVgLog(); + void stopProcess(); void killProcess(); + void checkParserFinished(); private: QString saveFname; Modified: branches/valkyrie_qt4port/objects/valgrind_object.cpp =================================================================== --- branches/valkyrie_qt4port/objects/valgrind_object.cpp 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/objects/valgrind_object.cpp 2010-03-03 22:14:50 UTC (rev 479) @@ -56,8 +56,10 @@ Valgrind::~Valgrind() { - // m_toolObjList.setAutoDelete( true ); - // m_toolObjList.clear(); + // clean up tool list + while (!toolObjList.isEmpty()) { + delete toolObjList.takeFirst(); + } } Modified: branches/valkyrie_qt4port/objects/valkyrie_object.cpp =================================================================== --- branches/valkyrie_qt4port/objects/valkyrie_object.cpp 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/objects/valkyrie_object.cpp 2010-03-03 22:14:50 UTC (rev 479) @@ -738,8 +738,7 @@ vk_assert( tool != 0 ); tool->stop(); -//TODO -// vk_assert( !tool->isRunning() ); + vk_assert( !tool->isRunning() ); } Modified: branches/valkyrie_qt4port/utils/vglogreader.cpp =================================================================== --- branches/valkyrie_qt4port/utils/vglogreader.cpp 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/utils/vglogreader.cpp 2010-03-03 22:14:50 UTC (rev 479) @@ -78,6 +78,7 @@ vglog = alog; node = doc; m_finished = false; + m_started = false; } VgLogHandler::~VgLogHandler() @@ -178,6 +179,7 @@ node = doc; m_fatalMsg = QString(); m_finished = false; + m_started = true; return true; } Modified: branches/valkyrie_qt4port/utils/vglogreader.h =================================================================== --- branches/valkyrie_qt4port/utils/vglogreader.h 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/utils/vglogreader.h 2010-03-03 22:14:50 UTC (rev 479) @@ -82,6 +82,8 @@ QString fatalMsg() { return m_fatalMsg; } /* may have reached end of log even with fatal error */ bool finished() { return m_finished; } + /* let us find out when parsing has been started */ + bool started() { return m_started; } private: QDomDocument doc; @@ -90,6 +92,7 @@ QString m_fatalMsg; bool m_finished; + bool m_started; }; Modified: branches/valkyrie_qt4port/utils/vk_logpoller.cpp =================================================================== --- branches/valkyrie_qt4port/utils/vk_logpoller.cpp 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/utils/vk_logpoller.cpp 2010-03-03 22:14:50 UTC (rev 479) @@ -39,6 +39,7 @@ } +// start with interval msecs void VkLogPoller::start( int interval ) { timer->start( interval ); Modified: branches/valkyrie_qt4port/utils/vk_logpoller.h =================================================================== --- branches/valkyrie_qt4port/utils/vk_logpoller.h 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/utils/vk_logpoller.h 2010-03-03 22:14:50 UTC (rev 479) @@ -43,7 +43,7 @@ VkLogPoller( QObject * parent, const char * name ); ~VkLogPoller(); - void start( int interval=100 ); + void start( int interval=100 ); // msec void stop(); bool isActive(); int interval(); Modified: branches/valkyrie_qt4port/utils/vk_utils.cpp =================================================================== --- branches/valkyrie_qt4port/utils/vk_utils.cpp 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/utils/vk_utils.cpp 2010-03-03 22:14:50 UTC (rev 479) @@ -48,7 +48,8 @@ /* prints various info msgs to stdout --------------------------------- */ void vkPrint( const char* msg, ... ) { - const char* vkname = vkConfig ? vkConfig->vkName.toLatin1().constData() : ""; + QByteArray ba = vkConfig->vkName.toLatin1(); + const char* vkname = vkConfig ? ba.constData() : ""; va_list ap; va_start( ap, msg ); va_end( ap ); @@ -63,7 +64,8 @@ /* prints error msg -------------------------------------------------- */ void vkPrintErr( const char* msg, ... ) { - const char* vkname = vkConfig ? vkConfig->vkName.toLatin1().constData() : ""; + QByteArray ba = vkConfig->vkName.toLatin1(); + const char* vkname = vkConfig ? ba.constData() : ""; va_list ap; va_start( ap, msg ); va_end( ap ); @@ -79,7 +81,8 @@ void vkDebug( const char* msg, ... ) { #ifdef DEBUG - const char* vkname = vkConfig ? qPrintable( vkConfig->vkName ) : ""; + QByteArray ba = vkConfig->vkName.toLatin1(); + const char* vkname = vkConfig ? ba.constData() : ""; va_list ap; va_start( ap, msg ); va_end( ap ); @@ -107,7 +110,8 @@ unsigned int line, const char* fn ) { - const char* vkname = vkConfig ? vkConfig->vkName.toLatin1().constData() : ""; + QByteArray ba = vkConfig->vkName.toLatin1(); + const char* vkname = vkConfig ? ba.constData() : ""; vkPrintErr("Assertion 'never reached' failed,"); vkPrintErr(" at %s#%u:%s", file, line, fn ); @@ -136,12 +140,12 @@ if ( QFile::exists(unique) ) { /* fall back on mkstemp */ char* tmpname = vk_str_malloc( unique.length() + 10 ); - sprintf( tmpname, "%s.XXXXXX", unique.toLatin1().constData() ); + sprintf( tmpname, "%s.XXXXXX", qPrintable( unique ) ); int fd = mkstemp( tmpname ); if ( fd == -1 ) { /* something went wrong */ VK_DEBUG("failed to create unique filename from '%s'.", - filepath.toLatin1().constData() ); + qPrintable( filepath ) ); return QString::null; } unique = QString( tmpname ); Modified: branches/valkyrie_qt4port/utils/vk_utils.h =================================================================== --- branches/valkyrie_qt4port/utils/vk_utils.h 2010-03-01 20:59:43 UTC (rev 478) +++ branches/valkyrie_qt4port/utils/vk_utils.h 2010-03-03 22:14:50 UTC (rev 479) @@ -73,9 +73,11 @@ #if DEBUG_ON /* print debugging msg with file+line info to stderr ------------------- */ -# define VK_DEBUG(msg, args...) { \ - vkPrintErr("DEBUG: %s#%d:%s:", __FILE__, __LINE__, __PRETTY_FUNCTION__ ); \ - vkPrintErr(msg, ## args ); \ +// Not using (fmt, args...), (fmt, ##args), as QtCreator code checker doesn't like it. +// TODO: reintroduce when we start using a proper IDE +# define VK_DEBUG(...) { \ + vkPrintErr("DEBUG: %s#%d: %s:", __FILE__, __LINE__, __PRETTY_FUNCTION__ ); \ + vkPrintErr(__VA_ARGS__); \ vkPrintErr(" "); \ } #else |