diff --git a/src/musredit_qt5/musredit/PFitOutputHandler.cpp b/src/musredit_qt5/musredit/PFitOutputHandler.cpp index 527845d7..7d1da209 100644 --- a/src/musredit_qt5/musredit/PFitOutputHandler.cpp +++ b/src/musredit_qt5/musredit/PFitOutputHandler.cpp @@ -173,25 +173,49 @@ void PFitOutputHandler::processDone(int exitCode, QProcess::ExitStatus exitStatu qDebug() << "**ERROR** PFitOutputHandler::processDone: exitCode = " << exitCode << Qt::endl; #endif } - fQuitButton->setText("Done"); + + if (fQuitRequested) { + // the process has now actually terminated in response to quitButtonPressed(); close the dialog + accept(); + } else { + fQuitButton->setText("Done"); + } } //---------------------------------------------------------------------------------------------------- /** - *

If the quit button is pressed while the fit is still running, try to terminate musrfit, if this - * does not work, try to kill musrfit. + *

If the quit button is pressed while the fit is still running, request musrfit to terminate + * (SIGTERM) and disable the button; a single-shot timer escalates to kill() (SIGKILL) if musrfit + * has not reacted within a few seconds. The dialog is NOT closed here: processDone() (connected to + * QProcess::finished()) calls accept() once the process has actually terminated. + * + *

This deliberately never blocks on QProcess::waitForFinished(). A blocking wait here would + * freeze the whole (modal) dialog for up to its default 30 s timeout with no user feedback, which + * in practice led users to force-quit musredit itself while musrfit was still being terminated - + * orphaning the musrfit process, since ~PFitOutputHandler() (the actual terminate/kill/kill-9 + * safety net) never got the chance to run. */ void PFitOutputHandler::quitButtonPressed() { - // if the fitting is still taking place, kill it - if (fProc->state() == QProcess::Running) { - fProc->terminate(); - if (!fProc->waitForFinished()) { - fProc->kill(); - } + if (fProc->state() == QProcess::NotRunning) { + accept(); + return; } - accept(); + if (fQuitRequested) // termination already in progress, ignore repeated clicks + return; + fQuitRequested = true; + + fQuitButton->setEnabled(false); + fQuitButton->setText(tr("Terminating...")); + fProc->terminate(); + + // escalate to SIGKILL if musrfit does not react to SIGTERM in time + // (e.g. blocked in slow disk/NFS I/O); accept() happens via processDone() + QTimer::singleShot(5000, this, [this]() { + if (fProc->state() != QProcess::NotRunning) + fProc->kill(); + }); } //---------------------------------------------------------------------------------------------------- diff --git a/src/musredit_qt5/musredit/PFitOutputHandler.h b/src/musredit_qt5/musredit/PFitOutputHandler.h index 90108382..89235920 100644 --- a/src/musredit_qt5/musredit/PFitOutputHandler.h +++ b/src/musredit_qt5/musredit/PFitOutputHandler.h @@ -65,6 +65,7 @@ class PFitOutputHandler : public QDialog private: Q_PID fProcPID; ///< keeps the process PID std::unique_ptr fProc; ///< pointer to the musrfit process + bool fQuitRequested = false; ///< set once the user pressed Quit while the process was still running std::unique_ptr fVbox; ///< pointer to the dialog layout manager std::unique_ptr fOutput; ///< the captured musrfit output is written (read only) into this text edit object. diff --git a/src/musredit_qt6/musredit/PFitOutputHandler.cpp b/src/musredit_qt6/musredit/PFitOutputHandler.cpp index 8e100255..92c7c938 100644 --- a/src/musredit_qt6/musredit/PFitOutputHandler.cpp +++ b/src/musredit_qt6/musredit/PFitOutputHandler.cpp @@ -217,7 +217,13 @@ void PFitOutputHandler::processDone(int exitCode, QProcess::ExitStatus exitStatu if ((exitStatus == QProcess::CrashExit) && (exitCode != 0)) { qDebug() << "**ERROR** PFitOutputHandler::processDone: exitCode = " << exitCode << Qt::endl; } - fQuitButton->setText("Done"); + + if (fQuitRequested) { + // the process has now actually terminated in response to quitButtonPressed(); close the dialog + accept(); + } else { + fQuitButton->setText("Done"); + } } //---------------------------------------------------------------------------------------------------- @@ -226,25 +232,41 @@ void PFitOutputHandler::processDone(int exitCode, QProcess::ExitStatus exitStatu * * @details Handles user request to close the dialog. The behavior depends on * whether the fit is still running: - * - If running: Attempts to terminate musrfit gracefully using QProcess::terminate(). - * If graceful termination fails within the timeout period, forcefully kills - * the process using QProcess::kill(). - * - If finished: Simply closes the dialog. + * - If running: Requests graceful termination via QProcess::terminate() (SIGTERM) + * and disables the button. A single-shot timer escalates to QProcess::kill() + * (SIGKILL) if musrfit has not reacted within a few seconds. The dialog is + * NOT closed here; processDone() (connected to QProcess::finished()) calls + * accept() once the process has actually terminated. + * - If already finished: Simply closes the dialog. * - * After ensuring the process is stopped (or was already stopped), accepts the - * dialog (closes it with QDialog::Accepted result). + * @note This slot deliberately never blocks on QProcess::waitForFinished(). + * A blocking wait here would freeze the whole (modal) dialog for up to its + * default 30 s timeout with no user feedback, which in practice led users to + * force-quit musredit itself while musrfit was still being terminated - + * orphaning the musrfit process, since ~PFitOutputHandler() (the actual + * terminate/kill/kill-9 safety net) never got the chance to run. */ void PFitOutputHandler::quitButtonPressed() { - // if the fitting is still taking place, kill it - if (fProc->state() == QProcess::Running) { - fProc->terminate(); - if (!fProc->waitForFinished()) { - fProc->kill(); - } + if (fProc->state() == QProcess::NotRunning) { + accept(); + return; } - accept(); + if (fQuitRequested) // termination already in progress, ignore repeated clicks + return; + fQuitRequested = true; + + fQuitButton->setEnabled(false); + fQuitButton->setText(tr("Terminating...")); + fProc->terminate(); + + // escalate to SIGKILL if musrfit does not react to SIGTERM in time + // (e.g. blocked in slow disk/NFS I/O); accept() happens via processDone() + QTimer::singleShot(5000, this, [this]() { + if (fProc->state() != QProcess::NotRunning) + fProc->kill(); + }); } //---------------------------------------------------------------------------------------------------- diff --git a/src/musredit_qt6/musredit/PFitOutputHandler.h b/src/musredit_qt6/musredit/PFitOutputHandler.h index a7a5a605..2bac163c 100644 --- a/src/musredit_qt6/musredit/PFitOutputHandler.h +++ b/src/musredit_qt6/musredit/PFitOutputHandler.h @@ -132,6 +132,7 @@ class PFitOutputHandler : public QDialog */ qint64 fProcPID; ///< Process ID of the running musrfit process. std::unique_ptr fProc; ///< QProcess object managing the musrfit execution. + bool fQuitRequested = false; ///< Set once the user pressed Quit while the process was still running. /** @} */ /** @name UI Components