[education/rkward/releases/0.8.1] rkward: Fix corner case in interrupting user commands
Thomas Friedrichsmeier
null at kde.org
Fri Apr 18 13:39:18 BST 2025
Git commit f7a220b2998f0b4e1e58ad3e69a435cb953f3778 by Thomas Friedrichsmeier.
Committed on 18/04/2025 at 12:39.
Pushed by tfry into branch 'releases/0.8.1'.
Fix corner case in interrupting user commands
M +14 -9 rkward/autotests/core_test.cpp
M +2 -1 rkward/rbackend/rkrbackend.cpp
https://invent.kde.org/education/rkward/-/commit/f7a220b2998f0b4e1e58ad3e69a435cb953f3778
diff --git a/rkward/autotests/core_test.cpp b/rkward/autotests/core_test.cpp
index e894ae55e..b728dd572 100644
--- a/rkward/autotests/core_test.cpp
+++ b/rkward/autotests/core_test.cpp
@@ -70,17 +70,19 @@ class RKWardCoreTest: public QObject {
Q_OBJECT
void runCommandWithTimeout(RCommand *command, RCommandChain* chain, std::function<void(RCommand*)> callback, int timeoutms = 1000) {
+ static int done;
QString ccopy = command->command();
+ auto command_id = command->id();
QElapsedTimer t;
t.start();
- bool done = false;
- bool *_done = &done;
- connect(command->notifier(), &RCommandNotifier::commandFinished, this, [_done, callback](RCommand *command) { *_done = true; callback(command); });
+ done = -1;
+ int *_done = &done;
+ connect(command->notifier(), &RCommandNotifier::commandFinished, this, [_done, command_id, callback](RCommand *command) { *_done = command_id; callback(command); });
RInterface::issueCommand(command, chain);
- while (!done && (t.elapsed() < timeoutms)) {
+ while ((done != command_id) && (t.elapsed() < timeoutms)) {
qApp->processEvents(QEventLoop::AllEvents, 500);
}
- if (!done) {
+ if (done != command_id) {
testLog("Command timed out: %s", qPrintable(ccopy));
QFAIL("Command timed out");
}
@@ -402,18 +404,21 @@ private Q_SLOTS:
void priorityCommandTest() {
bool priority_command_done = false;
- runCommandAsync(new RCommand("cat(\"sleeping\n\"); Sys.sleep(5)", RCommand::User), nullptr, [&priority_command_done](RCommand *command) {
+ runCommandAsync(new RCommand("\ncat(\"sleeping\\n\"); Sys.sleep(5)", RCommand::User), nullptr, [&priority_command_done](RCommand *command) {
QVERIFY(priority_command_done);
QVERIFY(command->failed());
QVERIFY(command->wasCanceled());
});
- auto priority_command = new RCommand("cat(\"something\\n\")", RCommand::PriorityCommand | RCommand::App);
+ auto priority_command = new RCommand("cat(\"priority\\n\")", RCommand::PriorityCommand | RCommand::App);
runCommandAsync(priority_command, nullptr, [&priority_command_done](RCommand *) {
priority_command_done = true;
RInterface::instance()->cancelAll();
});
- waitForAllFinished();
- waitForAllFinished(5000); // priority_command_done must remain in scope until done (even if interrupting fails for some reason)
+ // NOTE: The above two commands may or may not run in that order. Conceivably, the priority command gets handled, before the initial sleep command even started.
+ // The newline in the first command actually makes it a bit more likely for the priority command to go first (because parsing needs more iterations).
+ // We try to step on that interesting corner case, deliberately, at is has been causing failures in the past.
+ waitForAllFinished(); // first wait with a short timeout: sleep should have been cancelled
+ waitForAllFinished(5000); // fallbacck: priority_command_done must remain in scope until done (even if interrupting fails for some reason)
}
void RKConsoleHistoryTest() {
diff --git a/rkward/rbackend/rkrbackend.cpp b/rkward/rbackend/rkrbackend.cpp
index 6e94a1925..a884fdaf0 100644
--- a/rkward/rbackend/rkrbackend.cpp
+++ b/rkward/rbackend/rkrbackend.cpp
@@ -164,9 +164,11 @@ void RKTransmitNextUserCommandChunk (unsigned char* buf, int buflen) {
RKRBackend::repl_status.user_command_completely_transmitted = true;
}
buf[++pos] = '\0';
+ RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandTransmitted;
if (reached_newline || reached_eof) {
// Making this request synchronous is a bit painful. However, without this, it's extremely difficult to get correct interleaving of output and command lines
+ RKRSupport::InterruptSuspension susp; // This could also result in interrupts, in corner cases, so lets suspend those, for the minute
RBackendRequest req (true, RBackendRequest::CommandLineIn);
req.params["commandid"] = RKRBackend::this_pointer->current_command->id;
RKRBackend::this_pointer->handleRequest (&req);
@@ -224,7 +226,6 @@ int RReadConsole (const char* prompt, unsigned char* buf, int buflen, int hist)
RKRBackend::repl_status.user_command_successful_up_to = 0;
RKRBackend::repl_status.user_command_buffer = RKTextCodec::toNative(command->command);
RKTransmitNextUserCommandChunk (buf, buflen);
- RKRBackend::repl_status.user_command_status = RKRBackend::RKReplStatus::UserCommandTransmitted;
return 1;
}
} else if (RKRBackend::repl_status.user_command_status == RKRBackend::RKReplStatus::UserCommandTransmitted) {
More information about the rkward-tracker
mailing list