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