[education/rkward] rkward: Move all startup error handling into the setup wizard, itself.
Thomas Friedrichsmeier
null at kde.org
Sat Jun 8 23:21:01 BST 2024
Git commit 809605e272e6fc72929c2cf9a1d6a513f6900ba2 by Thomas Friedrichsmeier.
Committed on 08/06/2024 at 22:10.
Pushed by tfry into branch 'master'.
Move all startup error handling into the setup wizard, itself.
Fix some race-condition quirks while restarting a failed backend.
M +40 -9 rkward/dialogs/rksetupwizard.cpp
M +1 -0 rkward/dialogs/rksetupwizard.h
M +2 -0 rkward/misc/rkcommandlineargs.h
M +4 -4 rkward/rbackend/rkfrontendtransmitter.cpp
M +3 -1 rkward/rbackend/rkfrontendtransmitter.h
M +3 -3 rkward/rbackend/rkrbackendprotocol_backend.cpp
M +8 -12 rkward/rbackend/rkrbackendprotocol_frontend.cpp
M +1 -3 rkward/rbackend/rkrbackendprotocol_frontend.h
M +46 -29 rkward/rbackend/rkrinterface.cpp
M +14 -1 rkward/rbackend/rkrinterface.h
M +4 -1 rkward/rkward.cpp
https://invent.kde.org/education/rkward/-/commit/809605e272e6fc72929c2cf9a1d6a513f6900ba2
diff --git a/rkward/dialogs/rksetupwizard.cpp b/rkward/dialogs/rksetupwizard.cpp
index 0183265a8..88295fbdd 100644
--- a/rkward/dialogs/rksetupwizard.cpp
+++ b/rkward/dialogs/rksetupwizard.cpp
@@ -30,6 +30,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include "../settings/rksettingsmodulegeneral.h"
#include "../settings/rksettings.h"
#include "../misc/rkcommonfunctions.h"
+#include "../misc/rkcommandlineargs.h"
#include "../misc/rkstandardicons.h"
#include "../dialogs/rkloadlibsdialog.h"
#include "../windows/katepluginintegration.h"
@@ -88,6 +89,7 @@ public:
};
bool RKSetupWizard::has_been_run = false;
+bool RKSetupWizard::wizard_active = false;
static auto iconForStatus(RKSetupWizardItem::Status status) {
QString icon_id;
@@ -110,12 +112,30 @@ public:
h->addWidget(rstatus_icon);
h->addWidget(rstatus_label);
h->setStretch(1, 2);
+ detail_button = new QPushButton(i18n("Show error details"));
+ connect(detail_button, &QPushButton::clicked, this, [this]() {
+ if (!backend_error.details.isEmpty()) {
+ // WORKAROUND for silly KMessageBox behavior. (still needed in KF6 6.3.0)
+ // If length of details <= 512, it tries to show the details as a QLabel.
+ backend_error.details = backend_error.details.leftJustified(513);
+ }
+ KMessageBox::detailedError(nullptr, backend_error.message, backend_error.details, backend_error.title, KMessageBox::Notify | KMessageBox::AllowLink);
+ });
+ h->addWidget(detail_button);
update();
}
void update() {
+ rinst_label->setText(i18n("RKWard is currently using the R installation at <nobr>%1</nobr>.", RKSessionVars::RBinary()));
+ detail_button->hide();
if (RInterface::instance()->backendIsDead()) {
rstatus_icon->setPixmap(iconForStatus(RKSetupWizardItem::Error));
- rstatus_label->setText(i18n("<b>R backend has crashed.</b>"));
+ backend_error = RInterface::instance()->backendError();
+ detail_button->show();
+ if (RInterface::instance()->backendFailedToStart()) {
+ rstatus_label->setText(i18n("<b>R backend has failed to start.</b>"));
+ } else {
+ rstatus_label->setText(i18n("<b>R backend has crashed.</b>"));
+ }
if (anim) {
delete anim;
anim = nullptr;
@@ -138,7 +158,6 @@ public:
anim = nullptr;
}
} else {
- rinst_label->setText(i18n("RKWard is currently using the R installation at <nobr>%1</nobr>.", RKSessionVars::RBinary()));
rstatus_label->setText(i18n("<b>Waiting for R backend...</b>"));
if (!anim) anim = RKStandardIcons::busyAnimation(this, [this](const QIcon& icon) {
rstatus_icon->setPixmap(icon.pixmap(32, 32));
@@ -147,7 +166,9 @@ public:
}
private:
QLabel *rstatus_label, *rstatus_icon, *rinst_label;
+ QPushButton *detail_button;
QTimer *anim;
+ RInterface::BackendError backend_error;
};
class RBackendSelectionWidget : public QGroupBox {
@@ -274,20 +295,27 @@ RKSetupWizardItem* makeSoftwareCheck(const QString &exename, const QString& expl
RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QList<RKSetupWizardItem*> &settings_items) : KAssistantDialog(parent), reason(reason) {
RK_TRACE (DIALOGS);
+ RK_ASSERT(!wizard_active);
+ wizard_active = true;
// Cover page
auto page = new RKSetupWizardPage(this, i18n("RKWard Setup Assistant"));
auto l = new QVBoxLayout(page);
- QString intro = i18n("<p>This dialog will guide you through a quick check of the basic setup of the required (or recommended) components.</p>");
+ l->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("<p>This dialog will guide you through a quick check of the basic setup of the required (or recommended) components.</p>")));
if (reason == NewVersionRKWard) {
- intro += i18n("<p>The setup assistant has been invoked, automatically, because a new version of RKWard has been detected.</p>");
+ l->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("<p>The setup assistant has been invoked, automatically, because a new version of RKWard has been detected.</p>")));
} else if (reason == NewVersionR) {
// TODO: invoke this!
- intro += i18n("<p>The setup assistant has been invoked, automatically, because a new version of R has been detected.</p>");
+ l->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("<p>The setup assistant has been invoked, automatically, because a new version of R has been detected.</p>")));
} else if (reason == ProblemsDetected) {
- intro += i18n("<p>The setup assistant has been invoked, automatically, because a problem has been detected in your setup.</p>");
+ auto hl = new QHBoxLayout();
+ l->addLayout(hl);
+ auto lab = new QLabel();
+ lab->setPixmap(iconForStatus(RKSetupWizardItem::Error));
+ hl->addWidget(lab);
+ hl->addWidget(RKCommonFunctions::wordWrappedLabel("<p>The setup assistant has been invoked, automatically, because a problem has been detected in your setup.</p>"));
+ hl->setStretch(1,2);
}
- l->addWidget(RKCommonFunctions::wordWrappedLabel(intro));
l->addStretch();
// Basic installation page
@@ -373,6 +401,7 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
bool restart_needed = (select->group->checkedId() != 0) || RInterface::instance()->backendIsDead();
if (restart_needed) {
RKSessionVars::r_binary = select->selectedOpt();
+ RKCommandLineArgs::instance->set(RKCommandLineArgs::Setup, QVariant(true));
if (RKWardMainWindow::getMain()->triggerBackendRestart(this->reason == ManualCheck)) {
select->hide();
setValid(pageref, false);
@@ -458,6 +487,7 @@ RKSetupWizard::~RKSetupWizard() {
for(int i = 0; i < items.size(); ++i) {
delete items[i];
}
+ wizard_active = false;
}
void RKSetupWizard::next() {
@@ -475,7 +505,7 @@ void RKSetupWizard::doAutoCheck() {
// query settings modules for any problems
QList<RKSetupWizardItem*> settings_items = RKSettings::validateSettingsInteractive();
// check for those, and some cheap-but-important basics
- if (RKCommonFunctions::getRKWardDataDir ().isEmpty () || RKSettingsModulePlugins::pluginMaps().isEmpty() || (RKWardMainWindow::getMain()->katePluginIntegration()->knownPluginCount() == 0) || !settings_items.isEmpty()) {
+ if (RInterface::instance()->backendIsDead() || RKCommonFunctions::getRKWardDataDir().isEmpty () || RKSettingsModulePlugins::pluginMaps().isEmpty() || (RKWardMainWindow::getMain()->katePluginIntegration()->knownPluginCount() == 0) || !settings_items.isEmpty()) {
fullInteractiveCheck(ProblemsDetected, settings_items);
} else if (RKSettingsModuleGeneral::rkwardVersionChanged()) {
fullInteractiveCheck(NewVersionRKWard, settings_items);
@@ -490,12 +520,13 @@ void RKSetupWizard::manualCheck() {
void RKSetupWizard::fullInteractiveCheck(InvokationReason reason, const QList<RKSetupWizardItem*> &settings_items) {
RK_TRACE (DIALOGS);
+ if (wizard_active) return; // This can actually happen: In particular, if backend fails to start
if (has_been_run && reason != ManualCheck) return;
has_been_run = true;
auto wizard = new RKSetupWizard(RKWardMainWindow::getMain(), reason, settings_items);
-
auto res = wizard->exec();
+
if (res == QDialog::Accepted) {
if (!wizard->packages_to_install.isEmpty()) {
RKLoadLibsDialog::showInstallPackagesModal(wizard, nullptr, wizard->packages_to_install);
diff --git a/rkward/dialogs/rksetupwizard.h b/rkward/dialogs/rksetupwizard.h
index a141e85a5..ad5c72df8 100644
--- a/rkward/dialogs/rksetupwizard.h
+++ b/rkward/dialogs/rksetupwizard.h
@@ -47,6 +47,7 @@ friend class RKSetupWizardPage;
QList<RKSetupWizardItem*> items;
bool reinstallation_required;
InvokationReason reason;
+ static bool wizard_active;
};
class QComboBox;
diff --git a/rkward/misc/rkcommandlineargs.h b/rkward/misc/rkcommandlineargs.h
index dcd2df0a6..a106557ce 100644
--- a/rkward/misc/rkcommandlineargs.h
+++ b/rkward/misc/rkcommandlineargs.h
@@ -44,6 +44,8 @@ public:
return instance->storage[op];
};
private:
+friend class RKSetupWizard;
+ void set(const Option op, const QVariant value) { storage[op] = value; };
QList<QVariant> storage;
static RKCommandLineArgs *instance;
};
diff --git a/rkward/rbackend/rkfrontendtransmitter.cpp b/rkward/rbackend/rkfrontendtransmitter.cpp
index 43210b006..9f6d2ce21 100644
--- a/rkward/rbackend/rkfrontendtransmitter.cpp
+++ b/rkward/rbackend/rkfrontendtransmitter.cpp
@@ -85,7 +85,7 @@ void removeFromPathList (const char* varname, bool (*shouldRemove)(const QString
qputenv(varname, newlist.join(PATH_VAR_SEP).toLocal8Bit());
}
-RKFrontendTransmitter::RKFrontendTransmitter () : RKAbstractTransmitter () {
+RKFrontendTransmitter::RKFrontendTransmitter(RKRBackendProtocolFrontend *frontend) : RKAbstractTransmitter(), frontend(frontend) {
RK_TRACE (RBACKEND);
rkd_transmitter = new RKGraphicsDeviceFrontendTransmitter ();
@@ -301,7 +301,7 @@ void RKFrontendTransmitter::requestReceived (RBackendRequest* request) {
if (handleOutput (out->output, out->output.length (), out->type)) {
RKRBackendEvent* event = new RKRBackendEvent (new RBackendRequest (false, RBackendRequest::OutputStartedNotification));
- qApp->postEvent (RKRBackendProtocolFrontend::instance (), event);
+ qApp->postEvent(frontend, event);
}
delete (out);
@@ -314,7 +314,7 @@ void RKFrontendTransmitter::requestReceived (RBackendRequest* request) {
}
RKRBackendEvent* event = new RKRBackendEvent (request);
- qApp->postEvent (RKRBackendProtocolFrontend::instance (), event);
+ qApp->postEvent(frontend, event);
}
void RKFrontendTransmitter::backendExit (int exitcode) {
@@ -340,7 +340,7 @@ void RKFrontendTransmitter::handleTransmissionError (const QString &message) {
RBackendRequest* req = new RBackendRequest (false, RBackendRequest::BackendExit);
req->params["message"] = message;
RKRBackendEvent* event = new RKRBackendEvent (req);
- qApp->postEvent (RKRBackendProtocolFrontend::instance (), event);
+ qApp->postEvent(frontend, event);
exit ();
}
diff --git a/rkward/rbackend/rkfrontendtransmitter.h b/rkward/rbackend/rkfrontendtransmitter.h
index ef14c5917..eb0bd8b56 100644
--- a/rkward/rbackend/rkfrontendtransmitter.h
+++ b/rkward/rbackend/rkfrontendtransmitter.h
@@ -14,11 +14,12 @@ class QProcess;
class QIODevice;
class QLocalServer;
class RKGraphicsDeviceFrontendTransmitter;
+class RKRBackendProtocolFrontend;
class RKFrontendTransmitter : public RKAbstractTransmitter, public RKROutputBuffer {
Q_OBJECT
public:
- RKFrontendTransmitter ();
+ RKFrontendTransmitter(RKRBackendProtocolFrontend *frontend);
~RKFrontendTransmitter ();
void run () override;
@@ -41,6 +42,7 @@ private:
bool quirkmode;
QProcess* backend;
QLocalServer* server;
+ RKRBackendProtocolFrontend* frontend;
RKGraphicsDeviceFrontendTransmitter* rkd_transmitter;
};
diff --git a/rkward/rbackend/rkrbackendprotocol_backend.cpp b/rkward/rbackend/rkrbackendprotocol_backend.cpp
index 02e670619..4289d3591 100644
--- a/rkward/rbackend/rkrbackendprotocol_backend.cpp
+++ b/rkward/rbackend/rkrbackendprotocol_backend.cpp
@@ -178,15 +178,15 @@ void RKRBackendProtocolBackend::sendRequest (RBackendRequest *_request) {
}
RKRBackendEvent* event = new RKRBackendEvent (request);
RK_ASSERT (request->type != RBackendRequest::Output);
- qApp->postEvent (RKRBackendTransmitter::instance (), event);
+ qApp->postEvent(p_transmitter, event);
}
bool RKRBackendProtocolBackend::inRThread () {
return (QThread::currentThread () == instance ()->r_thread);
}
-void RKRBackendProtocolBackend::msleep (int delay) {
- static_cast<RKRBackendTransmitter*> (RKRBackendTransmitter::instance ())->publicmsleep (delay);
+void RKRBackendProtocolBackend::msleep(int delay) {
+ p_transmitter->publicmsleep(delay);
}
QString RKRBackendProtocolBackend::backendDebugFile () {
diff --git a/rkward/rbackend/rkrbackendprotocol_frontend.cpp b/rkward/rbackend/rkrbackendprotocol_frontend.cpp
index e82adeab4..1e562734c 100644
--- a/rkward/rbackend/rkrbackendprotocol_frontend.cpp
+++ b/rkward/rbackend/rkrbackendprotocol_frontend.cpp
@@ -1,6 +1,6 @@
/*
rkrbackendprotocol - This file is part of RKWard (https://rkward.kde.org). Created: Thu Nov 04 2010
-SPDX-FileCopyrightText: 2010-2022 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2010-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -16,32 +16,28 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include "../debug.h"
-RKRBackendProtocolFrontend* RKRBackendProtocolFrontend::_instance = nullptr;
RKRBackendProtocolFrontend::RKRBackendProtocolFrontend (RInterface* parent) : QObject (parent) {
RK_TRACE (RBACKEND);
- RK_ASSERT (!_instance);
frontend = parent;
- _instance = this;
}
-RKRBackendProtocolFrontend::~RKRBackendProtocolFrontend () {
+RKRBackendProtocolFrontend::~RKRBackendProtocolFrontend() {
RK_TRACE (RBACKEND);
- RK_ASSERT(_instance == this);
terminateBackend ();
- RKFrontendTransmitter::instance ()->wait(1000); // Wait for thread to catch the backend's exit request, and exit()
- QMetaObject::invokeMethod(RKFrontendTransmitter::instance(), &RKFrontendTransmitter::quit, Qt::QueuedConnection); // tell it to quit, otherwise
- RKFrontendTransmitter::instance ()->wait(3000); // Wait for thread to quit and clean up.
+ auto transmitter = RKFrontendTransmitter::instance();
+ transmitter->wait(1000); // Wait for thread to catch the backend's exit request, and exit()
+ QMetaObject::invokeMethod(transmitter, &RKFrontendTransmitter::quit, Qt::QueuedConnection); // tell it to quit, otherwise
+ transmitter->wait(3000); // Wait for thread to quit and clean up.
qApp->processEvents(QEventLoop::AllEvents, 500); // Not strictly needed, but avoids some mem leaks on exit by handling all posted BackendExit events
- delete RKFrontendTransmitter::instance ();
- _instance = nullptr;
+ delete transmitter;
}
void RKRBackendProtocolFrontend::setupBackend () {
RK_TRACE (RBACKEND);
- new RKFrontendTransmitter ();
+ new RKFrontendTransmitter(this);
}
void RKRBackendProtocolFrontend::setRequestCompleted (RBackendRequest *request) {
diff --git a/rkward/rbackend/rkrbackendprotocol_frontend.h b/rkward/rbackend/rkrbackendprotocol_frontend.h
index 34e30bb87..c0a831af8 100644
--- a/rkward/rbackend/rkrbackendprotocol_frontend.h
+++ b/rkward/rbackend/rkrbackendprotocol_frontend.h
@@ -1,6 +1,6 @@
/*
rkrbackendprotocol - This file is part of the RKWard project. Created: Thu Nov 04 2010
-SPDX-FileCopyrightText: 2010-2013 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2010-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -27,13 +27,11 @@ public:
static void sendPriorityCommand (RCommandProxy *proxy);
void terminateBackend ();
void setupBackend ();
- static RKRBackendProtocolFrontend* instance () { return _instance; };
protected:
/** needed to handle the QEvents, the R thread is sending (notifications on what's happening in the backend thread) */
void customEvent (QEvent *e) override;
QThread* main_thread;
private:
- static RKRBackendProtocolFrontend* _instance;
RInterface *frontend;
};
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index cfae930ba..67ad7607b 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -79,6 +79,7 @@ RInterface::RInterface () {
previously_idle = false;
locked = 0;
backend_dead = false;
+ backend_started = false;
dummy_command_on_stack = nullptr;
// Create a fake init command. This is the top level command designed to capture all output of the startup sequence.
@@ -86,34 +87,26 @@ RInterface::RInterface () {
// (sub)-commands to set everything up (see there).
auto fake_c = new RCommand(i18n("R Startup"), RCommand::App | RCommand::Sync | RCommand::ObjectListUpdate, i18n("R Startup"));
fake_c->whenFinished(this, [this](RCommand *command) {
- QString message = startup_errors;
- if (startup_phase2_error || command->failed()) message.append (i18n ("<p>\t-An unspecified error occurred that is not yet handled by RKWard. Likely RKWard will not function properly. Please check your setup.</p>\n"));
- if (!message.isEmpty ()) {
- message.prepend (i18n ("<p>There was a problem starting the R backend. The following error(s) occurred:</p>\n"));
-
- QString details = command->fullOutput().replace('<', "<").replace('\n', "<br>");
- if (!details.isEmpty ()) {
- // WORKAROUND for stupid KMessageBox behavior. (kdelibs 4.2.3)
- // If length of details <= 512, it tries to show the details as a QLabel.
- details = details.leftJustified (513);
- }
- KMessageBox::detailedError(nullptr, message, details, i18n("Error starting R"), KMessageBox::Notify | KMessageBox::AllowLink);
+ if (startup_phase2_error || command->failed()) backend_error.message.append (i18n ("<p>\t-An unspecified error occurred that is not yet handled by RKWard. Likely RKWard will not function properly. Please check your setup.</p>\n"));
+ if (!backend_error.message.isEmpty ()) {
+ backend_error.message.prepend (i18n ("<p>There was a problem starting the R backend. The following error(s) occurred:</p>\n"));
+ backend_error.details = command->fullOutput().replace('<', "<").replace('\n', "<br>");
+ backend_error.title = i18n("Error starting R");
+ // reporting the error will happen via RKWardMainWindow, which calls the setup wizard in this case
}
-
- startup_errors.clear ();
});
_issueCommand(fake_c);
- new RKSessionVars (this);
- new RKDebugHandler (this);
- new RKRBackendProtocolFrontend (this);
- RKRBackendProtocolFrontend::instance ()->setupBackend ();
+ new RKSessionVars(this);
+ new RKDebugHandler(this);
+ backendprotocol = new RKRBackendProtocolFrontend(this);
+ backendprotocol->setupBackend();
/////// Further initialization commands, which do not necessarily have to run before everything else can be queued, here. ///////
// NOTE: will receive the list as a call plain generic request from the backend ("updateInstalledPackagesList")
_issueCommand(new RCommand(".rk.get.installed.packages()", RCommand::App | RCommand::Sync));
- whenAllFinished(this, []() { RKSettings::validateSettingsInteractive (); });
+ whenAllFinished(this, [this]() { backend_started = !backend_dead; RKSettings::validateSettingsInteractive (); });
}
void RInterface::issueCommand(const QString &command, int type, const QString &rk_equiv, RCommandChain *chain) {
@@ -128,11 +121,18 @@ RInterface::~RInterface(){
// (noteably, it does call qApp->processEvents().
backend_dead = true;
RK_ASSERT(_instance == this);
- delete RKRBackendProtocolFrontend::instance ();
+ delete backendprotocol;
_instance = nullptr;
RKWindowCatcher::discardInstance ();
}
+void RInterface::reportFatalError() {
+ RK_TRACE(RBACKEND);
+
+ RKErrorDialog::reportableErrorMessage(nullptr, backend_error.message, backend_error.details, backend_error.title, backend_error.id);
+ backend_error = BackendError();
+}
+
bool RInterface::backendIsIdle () {
RK_TRACE (RBACKEND);
@@ -389,7 +389,7 @@ void RInterface::handleRequest (RBackendRequest* request) {
RKRBackendProtocolFrontend::setRequestCompleted(request);
} else if (request->type == RBackendRequest::Started) {
// The backend thread has finished basic initialization, but we still have more to do...
- startup_errors = request->params["message"].toString ();
+ backend_error.message.append(request->params["message"].toString());
command_requests.append (request);
RCommandChain *chain = openSubcommandChain (runningCommand ());
@@ -465,7 +465,7 @@ void RInterface::handleRequest (RBackendRequest* request) {
void RInterface::flushOutput (bool forced) {
// do not trace. called periodically
// RK_TRACE (RBACKEND);
- const ROutputList list = RKRBackendProtocolFrontend::instance ()->flushOutput (forced);
+ const ROutputList list = backendprotocol->flushOutput (forced);
for (ROutput *output : list) {
if (all_current_commands.isEmpty ()) {
@@ -516,6 +516,12 @@ void RInterface::issueCommand (RCommand *command, RCommandChain *chain) {
void RInterface::_issueCommand(RCommand *command, RCommandChain *chain) {
RK_TRACE (RBACKEND);
+ if (backend_dead) {
+ command->status |= RCommand::Failed;
+ handleCommandOut(command);
+ return;
+ }
+
if (command->command ().isEmpty ()) command->_type |= RCommand::EmptyCommand;
if (RKCarbonCopySettings::shouldCarbonCopyCommand (command)) {
command->_type |= RCommand::CCCommand;
@@ -567,7 +573,7 @@ void RInterface::cancelCommand (RCommand *command) {
RKDebugHandler::instance ()->sendCancel ();
} else {
RK_DEBUG(RBACKEND, DL_DEBUG, "Interrupting running command %d", command->id());
- RKRBackendProtocolFrontend::instance ()->interruptCommand (command->id ());
+ backendprotocol->interruptCommand (command->id ());
}
}
RCommandStackModel::getModel ()->itemChange (command);
@@ -882,19 +888,30 @@ void RInterface::processRBackendRequest (RBackendRequest *request) {
na_int = request->params["na_int"].toInt ();
} else if (type == RBackendRequest::BackendExit) {
if (!backend_dead) {
+ bool report = false;
backend_dead = true;
- Q_EMIT backendStatusChanged(Dead);
- if (!(request->params.value("regular", QVariant(false)).toBool())) { // irregular exit
- QString message = request->params["message"].toString ();
- RK_DEBUG(RBACKEND, DL_ERROR, "Backend exit: %s", qPrintable(message));
- message += i18n ("\nThe R backend will be shut down immediately. This means, you can not use any more functions that rely on it. I.e. you can do hardly anything at all, not even save the workspace (but if you're lucky, R already did that). What you can do, however, is save any open command-files, the output, or copy data out of open data editors. Quit RKWard after that. Sorry!");
- RKErrorDialog::reportableErrorMessage(nullptr, message, QString(), i18n("R engine has died"), "r_engine_has_died");
+ if (!request->params.value("regular", QVariant(false)).toBool()) { // irregular exit
+ backend_error.message.append(request->params["message"].toString());
+ RK_DEBUG(RBACKEND, DL_ERROR, "Backend exit: %s", qPrintable(backend_error.message));
+ if (backend_started) {
+ backend_error.message += i18n("\nThe R backend will be shut down immediately. This means, you can not use any more functions that rely on it. I.e. you can do hardly anything at all, not even save the workspace (but if you're lucky, R already did that). What you can do, however, is save any open command-files, the output, or copy data out of open data editors. Quit RKWard after that. Sorry!");
+ backend_error.title = i18n("R engine has died");
+ backend_error.id = "r_engine_has_died";
+ report = true;
+ } else if (all_current_commands.isEmpty()) {
+ // the fake startup command may not technically have started running, if exit occurred early on
+ RCommand *fake_startup_command = RCommandStack::currentCommand();
+ if (fake_startup_command) handleCommandOut(fake_startup_command);
+ // in any case, reporting the error will happen from there
+ }
}
while (!all_current_commands.isEmpty()) {
auto c = all_current_commands.takeLast();
c->status |= RCommand::Failed;
handleCommandOut(c);
}
+ Q_EMIT backendStatusChanged(Dead);
+ if (report) reportFatalError(); // TODO: Reporting should probably be moved to RKWardMinaWindow, entirely
}
} else {
RK_ASSERT (false);
diff --git a/rkward/rbackend/rkrinterface.h b/rkward/rbackend/rkrinterface.h
index b4e5f9a6b..ec8b2e1d4 100644
--- a/rkward/rbackend/rkrinterface.h
+++ b/rkward/rbackend/rkrinterface.h
@@ -17,6 +17,7 @@ class RCommand;
class RKWardMainWindow;
class RKRBackend;
class RBackendRequest;
+class RKRBackendProtocolFrontend;
/** This class provides the main interface to the R-processor.
@@ -78,9 +79,18 @@ not be interrupted. */
};
bool backendIsDead () const { return backend_dead; };
+/** implies backendIsDead() */
+ bool backendFailedToStart() const { return (backend_dead && !backend_started); }
bool backendIsIdle ();
static bool isNaReal (double value) { return na_real == value; };
static bool isNaInt (int value) { return na_int == value; };
+ struct BackendError{
+ QString title;
+ QString id;
+ QString message;
+ QString details;
+ };
+ BackendError backendError() const { return backend_error; };
private:
/** Calls RThread::flushOutput(), and takes care of adding the output to all applicable commands */
void flushOutput (bool forced);
@@ -113,6 +123,7 @@ private:
RCommandChain* openSubcommandChain (RCommand *parent_command);
QList<RCommand *> current_commands_with_subcommands;
void closeSubcommandChain (RCommand *parent_command);
+ void reportFatalError();
/** @see locked */
enum LockType {
@@ -124,14 +135,16 @@ private:
* May be an OR'ed combination of several LockType s, but currently, there is only one LockType */
int locked;
- QString startup_errors;
+ BackendError backend_error;
bool startup_phase2_error;
void runStartupCommand(RCommand *command, RCommandChain* chain, std::function<void(RCommand*)> callback);
RCommand *dummy_command_on_stack;
friend class RKRBackendProtocolFrontend;
bool backend_dead;
+ bool backend_started;
static double na_real;
static int na_int;
+ RKRBackendProtocolFrontend *backendprotocol;
friend class RKWardMainWindow;
friend class RCommand;
protected:
diff --git a/rkward/rkward.cpp b/rkward/rkward.cpp
index a3b2a9c2a..e54951fe8 100644
--- a/rkward/rkward.cpp
+++ b/rkward/rkward.cpp
@@ -388,7 +388,7 @@ void RKWardMainWindow::startR () {
}
RInterface::create();
- Q_EMIT(backendCreated());
+ Q_EMIT backendCreated();
connect(RInterface::instance(), &RInterface::backendStatusChanged, this, &RKWardMainWindow::setRStatus);
RObjectList::init();
@@ -963,6 +963,9 @@ void RKWardMainWindow::setRStatus (int status) {
setIndictatorColor(statusbar_r_status, KColorScheme::NegativeText, KColorScheme::NegativeBackground);
statusbar_r_status->setToolTip(i18n("The <b>R</b> engine is unavailable."));
interrupt_all_commands->setEnabled(false);
+ if (RInterface::instance()->backendFailedToStart()) {
+ RKSetupWizard::doAutoCheck(); // The wizard itself will prevent recursion, if alread active
+ }
}
}
More information about the rkward-tracker
mailing list