[education/rkward] rkward: Partial fix for command interruption mess
Thomas Friedrichsmeier
null at kde.org
Sun Sep 14 19:26:16 BST 2025
Git commit 5b37381fbea6b482e1f56e1bc9938f952755d51c by Thomas Friedrichsmeier.
Committed on 11/09/2025 at 14:54.
Pushed by tfry into branch 'master'.
Partial fix for command interruption mess
M +2 -1 rkward/autotests/core_test.cpp
M +115 -79 rkward/rbackend/rkrbackend.cpp
M +6 -7 rkward/rbackend/rkrbackend.h
M +4 -0 rkward/rbackend/rkrbackendprotocol_backend.cpp
M +1 -0 rkward/rbackend/rkrbackendprotocol_shared.cpp
M +1 -0 rkward/rbackend/rkrbackendprotocol_shared.h
M +5 -2 rkward/rbackend/rkreventloop.cpp
M +1 -1 rkward/rbackend/rkrinterface.cpp
M +1 -1 rkward/rbackend/rksignalsupport.cpp
https://invent.kde.org/education/rkward/-/commit/5b37381fbea6b482e1f56e1bc9938f952755d51c
diff --git a/rkward/autotests/core_test.cpp b/rkward/autotests/core_test.cpp
index 1a18131b0..4611b7225 100644
--- a/rkward/autotests/core_test.cpp
+++ b/rkward/autotests/core_test.cpp
@@ -197,6 +197,7 @@ class RKWardCoreTest : public QObject {
KAboutData::setApplicationData(about);
new RKCommandLineArgs(&about, qApp, true);
RK_Debug::RK_Debug_Level = DL_DEBUG;
+ // RK_Debug::RK_Debug_Flags = RBACKEND;
testLog(R_EXECUTABLE);
RKSessionVars::r_binary = QStringLiteral(R_EXECUTABLE);
main_win = new RKWardMainWindow();
@@ -479,7 +480,7 @@ class RKWardCoreTest : public QObject {
ScopeHandler sup([]() { RKSettingsModuleWatch::forTestingSuppressOutput(true); }, []() { RKSettingsModuleWatch::forTestingSuppressOutput(false); });
// NOTE: Test deliberately toned down until the next attempt to fully fix the issue. Still fails in corner-cases, typically 10-100 iterations are needed
// to trigger. Not considered much of a real-world issue, though.
- for (int i = 0; i < 1; ++i) {
+ for (int i = 0; i < 100; ++i) {
bool priority_command_done = false;
runCommandAsync(new RCommand(QStringLiteral("%1cat(\"sleeping\\n\");%1Sys.sleep(5);cat(\"BUG\")").arg(QString().fill(u'\n', i % 5)), RCommand::User), nullptr, [&priority_command_done](RCommand *command) {
QVERIFY(priority_command_done);
diff --git a/rkward/rbackend/rkrbackend.cpp b/rkward/rbackend/rkrbackend.cpp
index 2e3701e66..7f59f540e 100644
--- a/rkward/rbackend/rkrbackend.cpp
+++ b/rkward/rbackend/rkrbackend.cpp
@@ -75,9 +75,11 @@ static bool RK_IsRInterruptPending() {
#endif
}
+/// schedule an intterupt to happen at the next R_CheckUserInterrupt() (i.e. inside R code)
void RK_scheduleIntr() {
- RK_DEBUG(RBACKEND, DL_DEBUG, "interrupt scheduled");
+ // NOTE: signal handler code. Must not produce debug output, here (could cause mutex deadlocK)
RKRBackend::repl_status.interrupted = true;
+ RKRBackend::this_pointer->awaiting_sigint = false;
#ifdef Q_OS_WIN
ROb(UserBreak) = 1;
#else
@@ -85,11 +87,13 @@ void RK_scheduleIntr() {
#endif
}
+/// force an immediate intterupt (unless R_interrupts_suspended
void RK_doIntr() {
RK_scheduleIntr();
RFn::R_CheckUserInterrupt();
}
+/// thread-safe function to trigger an R interrupt, as if Ctrl+C had been hit in the R console
void RKRBackend::scheduleInterrupt() {
if (RKRBackendProtocolBackend::inRThread()) {
RK_scheduleIntr();
@@ -106,51 +110,86 @@ void RKRBackend::scheduleInterrupt() {
void RKRBackend::interruptCommand(int command_id) {
RK_TRACE(RBACKEND);
RK_ASSERT(command_id >= 0); // -1 would signify broken request
+ RK_ASSERT(!RKRBackendProtocolBackend::inRThread());
RK_DEBUG(RBACKEND, DL_DEBUG, "Received interrupt request for command id %d", command_id);
- QMutexLocker lock(&all_current_commands_mutex);
+ QMutexLocker lock(&command_flow_mutex);
/** A request to interrupt a command may happen at very different points in the code:
* 0) before it was even sent from the frontend -> frontend will not even send it to the backend code
* 1) after it was sent from the frontend, but before we properly started handling it in the backend (deserializing the request, pushing it onto
- * all_current_commands, feeding it into the REPL/eval) TODO: buggy!
+ * all_current_commands, feeding it into the REPL/eval) -> handled in beginAllowInterruptCommand()
* 2) when it is properly running in the backend -> this is the typical case, we worry about
- * 2b) when it is properly running in the backend, but there is also an active sub-command, which we should allow to complete
- * 3) after it finished running in the backend, but the frontend wasn't aware of that, yet TODO: buggy! */
- if (!all_current_commands.isEmpty() && (all_current_commands.last()->id == command_id)) {
- if (too_late_to_interrupt) {
- RK_DEBUG(RBACKEND, DL_DEBUG, "too late to interrupt command id %d (repl uc status: %d)", command_id, repl_status.user_command_status);
- } else {
- RK_DEBUG(RBACKEND, DL_DEBUG, "scheduling interrupt for command id %d (repl uc status: %d)", command_id, repl_status.user_command_status);
- lock.unlock();
- scheduleInterrupt();
- return;
- }
+ * 2b) when it is properly running in the backend, but there is also an active sub-command, which we should allow to complete -> TODO buggy
+ * 3) after it has finished running in the backend, but the frontend wasn't aware of that, yet TODO: buggy! */
+ if (current_command && (current_command->id == command_id) && (current_command->interruptible_stage)) {
+ RK_DEBUG(RBACKEND, DL_DEBUG, "scheduling immediate interrupt for command id %d", command_id);
+ awaiting_sigint = true;
+ scheduleInterrupt();
} else {
- bool any_found = false;
- // if the command to cancel is *not* the topmost command, then do not interrupt, yet.
- for (RCommandProxy *candidate : std::as_const(all_current_commands)) {
- if (candidate->id == command_id) {
- if (!current_commands_to_cancel.contains(candidate)) {
- RK_DEBUG(RBACKEND, DL_DEBUG, "scheduling delayed interrupt for command id %d", command_id);
- current_commands_to_cancel.append(candidate);
- any_found = true;
- }
- }
- }
- if (!any_found) {
- RK_DEBUG(RBACKEND, DL_ERROR, "interrupt scheduled for command id %d, but it is not current", command_id);
+ RK_DEBUG(RBACKEND, DL_DEBUG, "scheduling delayed interrupt for command id %d", command_id);
+ commands_to_cancel_deferred.append(command_id);
+ }
+}
+
+void RKRBackend::beginAllowInterruptCommand(RCommandProxy *command) {
+ if (command->type & RCommand::Internal) return;
+ RK_TRACE(RBACKEND);
+
+ bool intr = false;
+ {
+ QMutexLocker m(&command_flow_mutex);
+ RK_ASSERT(!command->interruptible_stage);
+ command->interruptible_stage = true;
+
+ if (commands_to_cancel_deferred.contains(command->id)) {
+ RK_DEBUG(RBACKEND, DL_DEBUG, "triggering deferred interrupt for command %d", command->id);
+ commands_to_cancel_deferred.removeAll(command->id);
+ intr = true;
}
}
+ if (intr) {
+ // do not call while mutex is locked!
+ RK_doIntr();
+ }
}
-void RKRBackend::clearPendingInterrupt() {
+void RKRBackend::endAllowInterruptCommand(RCommandProxy *command) {
+ if (command->type & RCommand::Internal) return;
RK_TRACE(RBACKEND);
- if (!RK_IsRInterruptPending()) return;
- RKRBackend::repl_status.interrupted = false;
- bool passed = RFn::R_ToplevelExec([](void *) { RFn::R_CheckUserInterrupt(); }, nullptr);
- if (!passed) RK_DEBUG(RBACKEND, DL_DEBUG, "pending interrupt cleared");
- RK_ASSERT(!passed);
+ bool wait_interrupt;
+ {
+ QMutexLocker m(&command_flow_mutex);
+ RK_ASSERT(command->interruptible_stage);
+ command->interruptible_stage = false;
+ wait_interrupt = awaiting_sigint;
+ }
+ // wait for any pending interrupt to happen
+ while (wait_interrupt) {
+ // do not call while mutex is locked!
+ RFn::R_CheckUserInterrupt();
+ RKRBackendProtocolBackend::msleep(10); // TODO: timeout after a while? In case interrupts are blocked, or something?
+ {
+ QMutexLocker m(&command_flow_mutex);
+ wait_interrupt = awaiting_sigint;
+ }
+ }
+}
+
+void RKRBackend::handleDeferredInterrupts() {
+ RK_TRACE(RBACKEND);
+ bool intr = false;
+ {
+ QMutexLocker m(&command_flow_mutex);
+ if (current_command && commands_to_cancel_deferred.contains(current_command->id) && current_command->interruptible_stage) {
+ RK_DEBUG(RBACKEND, DL_DEBUG, "triggering deferred interrupt for command %d", current_command->id);
+ commands_to_cancel_deferred.removeAll(current_command->id);
+ intr = true;
+ }
+ }
+ if (intr) {
+ RK_scheduleIntr();
+ }
}
static void markLastWarningAsErrorMessage() {
@@ -195,6 +234,7 @@ static void RKTransmitNextUserCommandChunk(unsigned char *buf, int buflen) {
}
buf[++pos] = '\0';
RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandTransmitted;
+ RK_DEBUG(RBACKEND, DL_DEBUG, "transmitted chunk %s", buf);
if (reached_newline || reached_eof) {
// feed a marker into the output stream that a source line of the command shall be interleaved into the output, at this point (if running in the console)
@@ -206,6 +246,12 @@ static void RKTransmitNextUserCommandChunk(unsigned char *buf, int buflen) {
void RBusy(int);
+static void replCommandFinished() {
+ RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand;
+ RKRBackend::this_pointer->commandFinished();
+ RKRBackend::this_pointer->handleDeferredInterrupts();
+}
+
int RReadConsole(const char *prompt, unsigned char *buf, int buflen, int hist) {
RK_TRACE(RBACKEND);
@@ -231,6 +277,7 @@ int RReadConsole(const char *prompt, unsigned char *buf, int buflen, int hist) {
if (!(command->type & RCommand::User)) {
RKRBackend::this_pointer->runCommand(command);
RKRBackend::this_pointer->commandFinished();
+ RKRBackend::this_pointer->handleDeferredInterrupts();
} else {
// so, we are about to transmit a new user command, which is quite a complex endeavor...
/* Some words about running user commands:
@@ -264,11 +311,6 @@ int RReadConsole(const char *prompt, unsigned char *buf, int buflen, int hist) {
RKTransmitNextUserCommandChunk(buf, buflen);
return 1;
}
- } else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandSyntaxError) {
- RBusy(1); // properly init command, so commandFinished() can end it
- RKRBackend::this_pointer->current_command->status |= RCommand::Failed | RCommand::ErrorSyntax;
- RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand;
- RKRBackend::this_pointer->commandFinished();
} else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandRunning) {
// This can mean three different things:
// 1) User called readline ()
@@ -289,21 +331,20 @@ int RReadConsole(const char *prompt, unsigned char *buf, int buflen, int hist) {
if (n_frames < 1) {
// No active frames? This can't be a call to readline(), so the previous command must have finished.
if (RKRBackend::repl_status.user_command_completely_transmitted) {
- RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand;
- RKRBackend::this_pointer->commandFinished();
+ replCommandFinished();
} else RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandTransmitted;
} else {
// A call to readline(). Will be handled below
break;
}
} else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandFailed) {
- RKRBackend::this_pointer->current_command->status |= RCommand::Failed | RCommand::ErrorOther;
- RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand;
- RKRBackend::this_pointer->commandFinished();
+ if (!(RKRBackend::this_pointer->current_command->status & RCommand::ErrorSyntax)) {
+ RKRBackend::this_pointer->current_command->status |= RCommand::Failed | RCommand::ErrorOther;
+ }
+ replCommandFinished();
} else {
RK_ASSERT(RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::ReplIterationKilled);
- RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::NoUserCommand;
- RKRBackend::this_pointer->commandFinished();
+ replCommandFinished();
}
}
}
@@ -349,7 +390,7 @@ int RReadConsole(const char *prompt, unsigned char *buf, int buflen, int hist) {
if (request.params[QStringLiteral("cancelled")].toBool()) {
if (RKRBackend::this_pointer->current_command) RKRBackend::this_pointer->current_command->status |= RCommand::Canceled;
RFn::Rf_error("cancelled"); // TODO: probably a memleak, as d'tors (request, params) won't be called
- RK_ASSERT(false); // should not reach this point.
+ RK_ASSERT(false); // should not reach this point.
}
QByteArray localres = RKTextCodec::toNative(request.params[QStringLiteral("result")].toString());
@@ -394,10 +435,9 @@ void RWriteConsoleEx(const char *buf, int buflen, int type) {
// output while nothing else is running (including handlers?) -> This may be a syntax error.
if ((RKRBackend::repl_status.eval_depth == 0) && (!RKRBackend::repl_status.browser_context) && (!RKRBackend::this_pointer->isKilled())) {
if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandTransmitted) {
- // status UserCommandTransmitted might have been set from RKToplevelStatementFinishedHandler, too, in which case all is fine
- // (we're probably inside another task handler at this point, then)
- if (RKRBackend::repl_status.user_command_parsed_up_to < RKRBackend::repl_status.user_command_transmitted_up_to) {
- RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandSyntaxError;
+ if (RKRBackend::repl_status.user_command_parsed_up_to < RKRBackend::repl_status.user_command_transmitted_up_to && !RKRBackend::repl_status.interrupted) {
+ RKRBackend::this_pointer->current_command->status |= RCommand::Failed | RCommand::ErrorSyntax;
+ RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandFailed;
}
} else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::ReplIterationKilled) {
// purge superfluous newlines and empty output
@@ -549,12 +589,14 @@ The interesting bit is in rresetconsole_called(). */
struct RKUserCommandErrorDetection {
bool reset_signals_error = true;
void rbusy_called(int busy) {
- reset_signals_error = busy;
+ if (busy) reset_signals_error = true;
+ RK_DEBUG(RBACKEND, DL_DEBUG, "RBusy %d", busy);
}
void reditfiles_called() {
reset_signals_error = false;
}
void rresetconsole_called() {
+ RK_DEBUG(RBACKEND, DL_WARNING, "reset console called, repl status %d, reset_signals_error %d, wasintr %d, awaiting_sigint %d", RKRBackend::repl_status.user_command_status, reset_signals_error, RKRBackend::repl_status.interrupted, RKRBackend::this_pointer->awaiting_sigint);
if (!reset_signals_error) {
reset_signals_error = true;
return;
@@ -566,9 +608,7 @@ struct RKUserCommandErrorDetection {
RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandFailed;
}
- // it is unlikely, but possible, that an interrupt signal was received, but the current command failed for some other reason, before processing was actually interrupted. In this case, R_interrupts_pending is not yet cleared.
- const bool command_was_interrupted = RKRBackend::repl_status.interrupted && !RK_IsRInterruptPending();
- if (!command_was_interrupted) {
+ if (!RKRBackend::repl_status.interrupted) {
markLastWarningAsErrorMessage();
RK_DEBUG(RBACKEND, DL_DEBUG, "error in user command");
}
@@ -718,6 +758,11 @@ void RBusy(int busy) {
}
RKRBackend::repl_status.user_command_parsed_up_to = RKRBackend::repl_status.user_command_transmitted_up_to;
RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandRunning;
+ RKRBackend::this_pointer->beginAllowInterruptCommand(RKRBackend::this_pointer->current_command);
+ }
+ } else if (RKRBackend::repl_status.user_command_status != RKRBackend::RKReplStatus::NoUserCommand && RKRBackend::repl_status.eval_depth == 0) {
+ if (RKRBackend::this_pointer->current_command->interruptible_stage) {
+ RKRBackend::this_pointer->endAllowInterruptCommand(RKRBackend::this_pointer->current_command);
}
}
}
@@ -949,7 +994,7 @@ bool RKRBackend::startR() {
RKSignalSupport::saveDefaultSignalHandlers();
- too_late_to_interrupt = false;
+ awaiting_sigint = false;
r_running = true;
int argc = 3;
char *argv[3] = {qstrdup("rkward"), qstrdup("--no-save"), qstrdup("--no-restore")};
@@ -1188,6 +1233,7 @@ static void parseAndRunWorker(void *_data) {
}
// evaluate the actual command - also, if it consists of multiple expressions, internally
+ RKRBackend::this_pointer->beginAllowInterruptCommand(data->command);
if (RFn::TYPEOF(parsed) == EXPRSXP && RFn::LENGTH(parsed) > 0) {
const int len = RFn::LENGTH(parsed);
for (int bi = 0; bi < len; bi++) {
@@ -1198,6 +1244,7 @@ static void parseAndRunWorker(void *_data) {
exp = RFn::Rf_eval(parsed, ROb(R_GlobalEnv));
}
RFn::Rf_protect(exp);
+ RKRBackend::this_pointer->endAllowInterruptCommand(data->command);
data->status = RKParseAndRunData::Evalled;
if (data->warn_level != 1) setWarnOption(data->warn_level);
@@ -1276,7 +1323,7 @@ void doPendingPriorityCommands() {
if (command) {
RK_DEBUG(RBACKEND, DL_DEBUG, "running priority command %s", qPrintable(command->command));
{
- QMutexLocker lock(&RKRBackend::this_pointer->all_current_commands_mutex);
+ QMutexLocker lock(&RKRBackend::this_pointer->command_flow_mutex);
RKRBackend::this_pointer->all_current_commands.append(command);
RKRBackend::this_pointer->current_command = command;
}
@@ -1286,10 +1333,13 @@ void doPendingPriorityCommands() {
RCommandProxy *previous_command_backup = RKRBackend::this_pointer->previous_command;
RKRBackend::this_pointer->commandFinished(false);
RKRBackend::this_pointer->previous_command = previous_command_backup;
- RBackendRequest req(false, RBackendRequest::CommandOut); // NOTE: We do *NOT* want a reply to this one, and in particular, we do *NOT* want to do
- // (recursive) event processing while handling this.
- req.command = command;
- RKRBackend::this_pointer->handleRequest(&req);
+ {
+ RBackendRequest req(false, RBackendRequest::CommandOut); // NOTE: We do *NOT* want a reply to this one, and in particular, we do *NOT* want to do
+ // (recursive) event processing while handling this.
+ req.command = command;
+ RKRBackend::this_pointer->handleRequest(&req);
+ }
+ RKRBackend::this_pointer->handleDeferredInterrupts();
}
}
@@ -1347,13 +1397,7 @@ void RKRBackend::run(const QString &locale_dir, bool setup) {
void RKRBackend::commandFinished(bool check_object_updates_needed) {
RK_TRACE(RBACKEND);
- RK_DEBUG(RBACKEND, DL_DEBUG, "done running command %s", qPrintable(current_command->command));
-
- {
- QMutexLocker lock(&all_current_commands_mutex);
- too_late_to_interrupt = true;
- }
- clearPendingInterrupt(); // Mutex must be unlocked for this!
+ RK_DEBUG(RBACKEND, DL_DEBUG, "done running command %s, status: %d", qPrintable(current_command->command), current_command->status);
fetchStdoutStderr(true);
if (current_command->type & RCommand::CCOutput) printAndClearCapturedMessages(current_command->type & RCommand::Plugin);
@@ -1380,15 +1424,13 @@ void RKRBackend::commandFinished(bool check_object_updates_needed) {
previous_command = current_command;
{
- QMutexLocker lock(&all_current_commands_mutex);
+ QMutexLocker lock(&command_flow_mutex);
if (repl_status.interrupted) {
repl_status.interrupted = false;
current_command->status |= RCommand::Canceled;
- clearPendingInterrupt(); // might not have been handled, yet
}
all_current_commands.pop_back();
if (!all_current_commands.isEmpty()) current_command = all_current_commands.last();
- too_late_to_interrupt = false;
}
}
@@ -1434,7 +1476,7 @@ RCommandProxy *RKRBackend::handleRequest2(RBackendRequest *request, bool mayHand
if (!command) return nullptr;
{
- QMutexLocker lock(&all_current_commands_mutex);
+ QMutexLocker lock(&command_flow_mutex);
RK_ASSERT(command != current_command);
all_current_commands.append(command);
current_command = command;
@@ -1445,18 +1487,12 @@ RCommandProxy *RKRBackend::handleRequest2(RBackendRequest *request, bool mayHand
while (command) {
runCommand(command);
commandFinished(false);
+ handleDeferredInterrupts();
command = fetchNextCommand();
};
- {
- QMutexLocker lock(&all_current_commands_mutex);
- if (current_commands_to_cancel.contains(current_command)) {
- RK_DEBUG(RBACKEND, DL_DEBUG, "will now interrupt parent command");
- current_commands_to_cancel.removeAll(current_command);
- scheduleInterrupt();
- }
- }
+ handleDeferredInterrupts();
return nullptr;
}
diff --git a/rkward/rbackend/rkrbackend.h b/rkward/rbackend/rkrbackend.h
index d5ca3ff4d..a7f683bce 100644
--- a/rkward/rbackend/rkrbackend.h
+++ b/rkward/rbackend/rkrbackend.h
@@ -133,7 +133,6 @@ class RKRBackend : public RKROutputBuffer {
enum {
NoUserCommand,
UserCommandTransmitted,
- UserCommandSyntaxError,
UserCommandRunning,
UserCommandFailed,
ReplIterationKilled
@@ -162,10 +161,13 @@ class RKRBackend : public RKROutputBuffer {
void printCommand(const QString &command);
void catToOutputFile(const QString &out);
- QMutex all_current_commands_mutex;
- QList<RCommandProxy *> current_commands_to_cancel;
- bool too_late_to_interrupt;
+ QMutex command_flow_mutex;
+ QList<int> commands_to_cancel_deferred;
void interruptCommand(int command_id);
+ void beginAllowInterruptCommand(RCommandProxy *command);
+ void endAllowInterruptCommand(RCommandProxy *command);
+ void handleDeferredInterrupts();
+ volatile bool awaiting_sigint;
/** check stdout and stderr for new output (from sub-processes). Since this function is called from both threads, it is protected by a mutex.
* @param forcibly: if false, and the other thread currently has a lock on the mutex, do nothing, and return false.
@@ -181,9 +183,6 @@ class RKRBackend : public RKROutputBuffer {
bool graphicsEngineMismatchMessage(int compiled_version, int runtime_version);
- private:
- void clearPendingInterrupt();
-
protected:
RCommandProxy *handleRequest(RBackendRequest *request, bool mayHandleSubstack);
RCommandProxy *handleRequest2(RBackendRequest *request, bool mayHandleSubstack);
diff --git a/rkward/rbackend/rkrbackendprotocol_backend.cpp b/rkward/rbackend/rkrbackendprotocol_backend.cpp
index b69a4bde7..37369b982 100644
--- a/rkward/rbackend/rkrbackendprotocol_backend.cpp
+++ b/rkward/rbackend/rkrbackendprotocol_backend.cpp
@@ -16,6 +16,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include <QLocalSocket>
#include <QMutex>
#include <QThread>
+#include <QTime>
#include <QUrl>
#include <QUuid> // mis-used as a random-string generator
@@ -36,6 +37,9 @@ void RKDebugMessageOutput(QtMsgType type, const QMessageLogContext &, const QStr
if (type == QtFatalMsg) {
fprintf(stderr, "%s\n", qPrintable(msg));
}
+ RK_Debug::debug_file->write("B ");
+ RK_Debug::debug_file->write(qPrintable(QString::number(QTime::currentTime().msecsSinceStartOfDay())));
+ RK_Debug::debug_file->write(": ");
RK_Debug::debug_file->write(qPrintable(msg));
RK_Debug::debug_file->write("\n");
RK_Debug::debug_file->flush();
diff --git a/rkward/rbackend/rkrbackendprotocol_shared.cpp b/rkward/rbackend/rkrbackendprotocol_shared.cpp
index 31710194c..237f1a4d2 100644
--- a/rkward/rbackend/rkrbackendprotocol_shared.cpp
+++ b/rkward/rbackend/rkrbackendprotocol_shared.cpp
@@ -16,6 +16,7 @@ RCommandProxy::RCommandProxy(const QString &command, int type) {
RCommandProxy::type = type;
id = -1;
status = 0;
+ interruptible_stage = false;
}
RCommandProxy::~RCommandProxy() {
diff --git a/rkward/rbackend/rkrbackendprotocol_shared.h b/rkward/rbackend/rkrbackendprotocol_shared.h
index 7e1dacf17..7aea84cae 100644
--- a/rkward/rbackend/rkrbackendprotocol_shared.h
+++ b/rkward/rbackend/rkrbackendprotocol_shared.h
@@ -127,6 +127,7 @@ class RCommandProxy : public RData {
int id;
int status;
int has_been_run_up_to;
+ bool interruptible_stage;
};
class RKROutputBuffer {
diff --git a/rkward/rbackend/rkreventloop.cpp b/rkward/rbackend/rkreventloop.cpp
index d2daabfd3..813af0cbe 100644
--- a/rkward/rbackend/rkreventloop.cpp
+++ b/rkward/rbackend/rkreventloop.cpp
@@ -14,7 +14,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
void RK_doIntr();
-static void processX11EventsWorker(void *) {
+static void processX11EventsWorker(void *ok) {
// this basically copied from R's unix/sys-std.c (Rstd_ReadConsole)
#ifndef Q_OS_WIN
for (;;) {
@@ -44,6 +44,7 @@ TODO: verify we really need this. */
timeout_counter = 0;
}
#endif
+ *static_cast<bool *>(ok) = true;
}
void RKREventLoop::processX11Events() {
@@ -51,10 +52,12 @@ void RKREventLoop::processX11Events() {
if (!RKRBackend::this_pointer->r_running) return;
if (RKRBackend::this_pointer->isKilled()) return;
+ bool ok = false;
RKRBackend::repl_status.eval_depth++;
// In case an error (or user interrupt) is caught inside processX11EventsWorker, we don't want to long-jump out.
- RFn::R_ToplevelExec(processX11EventsWorker, nullptr);
+ RFn::R_ToplevelExec(processX11EventsWorker, &ok);
RKRBackend::repl_status.eval_depth--;
+ if (!ok) RK_DEBUG(RBACKEND, DL_WARNING, "Error in process events");
}
static void (*RK_eventHandlerFunction)() = nullptr;
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index e37b17811..ec2241e98 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -473,7 +473,7 @@ void RInterface::flushOutput(bool forced) {
for (const ROutput &output : list) {
if (output.type == ROutput::CommandLineIn) {
RCommand *command = all_current_commands.value(0, nullptr); // User command will always be the first.
- if ((command == nullptr) || (!command->type() & RCommand::User)) {
+ if ((command == nullptr) || !(command->type() & RCommand::User)) {
RK_ASSERT(false);
} else {
command->commandLineIn();
diff --git a/rkward/rbackend/rksignalsupport.cpp b/rkward/rbackend/rksignalsupport.cpp
index 012add4eb..54ff91f2c 100644
--- a/rkward/rbackend/rksignalsupport.cpp
+++ b/rkward/rbackend/rksignalsupport.cpp
@@ -36,7 +36,7 @@ struct sigaction default_sigabrt_handler;
rk_sighandler_t r_sigint_handler = nullptr;
void (*new_sigint_handler)(void) = nullptr;
void internal_sigint_handler(int num) {
- RK_DEBUG(RBACKEND, DL_DEBUG, "calling internal sigint handler");
+ // NOTE: signal handler code. Must not produce debug output, here (could cause mutex deadlocK)
new_sigint_handler();
signal(num, internal_sigint_handler);
}
More information about the rkward-tracker
mailing list