Reputation: 9668
I have already exhaustively googled this topic, and the answer is "don't do it".
However I am kind of stuck between a rock and a hard place, so I need to thoroughly explore this topic before giving up on the concept of "forking a Qt application". Also, I think it can be useful as an exercise to fully understand the nature of Qt initialization.
The premise is that I need to create a new running instance of my current app on the fly. The new instance will run simultaneously with the parent instance.
I would like to create a function that is run to fork my running application. It should be robust enough to handle whatever existing state is present;
QTest::testCase()
or it is instantiated in a int main(int argc, char *argv[])
entrypoint. I don't know which way the app was instantiated.So my naive idea is basically the following;
Please see the implementation of my naive attempt below.
The problems are plentiful with this approach, but they mostly boil down to the following; Chasing down resources and closing them successfully is an infinite game of whack-a-mole, and the chance of succeeding with this is slim to none, especially across platforms and Qt versions. This is perfectly exemplified by my naive implementation that simply hangs forever when I try to instantiate a push button in the child after cleaning out the old state.
My next idea is to somehow replicate or call the code of the exit() system call and let it run to the point where the OS terminates the process. It seems there are a myriad of exec*()
, fork*()
and posix_*()
calls that perform variations of this. This is dandy if we don't care about platform independence. Worst case, we will go this route and implement our method in platform dependent way for all platforms we care about.
Please note that using QProcess
is not feasible because the code we want to run does not live in separate executable from the currently running program, and since we don't control how the parent is wrapped and/or instantiated, we can't simply start argv[0]
(for example the code may be started in a QTest
that takes full control of command line arguments).
My naive attempt looks like this;
testFork.hpp:
#ifndef TESTFORK_HPP
#define TESTFORK_HPP
#include "test/Common.hpp"
class TestFork:public QObject
{
Q_OBJECT
private slots:
void test();
};
#endif
// TESTFORK_HPP
testFork.cpp:
#include "TestFork.hpp"
#include <QDebug>
#include <QApplication>
#include <QPushButton>
#include <QTimer>
#include <QAbstractEventDispatcher>
#include <QOpenGLContext>
#include <QtDBus/QDBusConnection>
#include <csignal>
#include <unistd.h> // For fork
#include <sys/wait.h> // For waitpid
void TestFork::test()
{
qDebug() << "TEST FORK";
pid_t pid = fork();
if (pid < 0)
{
qCritical() << "Fork failed!";
return;
}
else if (pid == 0)
{
qDebug() << "In child process A1a";
QDBusConnection::disconnectFromBus("session");
qDebug() << "In child process A1b";
QDBusConnection::disconnectFromBus("system");
// Reset OpenGL contexts if they exist
if (QOpenGLContext::currentContext()) {
qDebug() << "In child process A6a";
QOpenGLContext::currentContext()->doneCurrent();
qDebug() << "In child process A6b";
QOpenGLContext::currentContext()->deleteLater();
}
qDebug() << "In child process A7a";
// Reset signal handlers to default
signal(SIGPIPE, SIG_DFL);
qDebug() << "In child process A7b";
signal(SIGCHLD, SIG_DFL);
qDebug() << "In child process A8";
/* Commented out so we can see log output but does not seem to make a difference.
close(0); // Close standard input
close(1); // Close standard output
close(2); // Close standard error
*/
qDebug() << "In child process A9";
for (int fd = 3; fd < 1024; ++fd) {
close(fd);
}
if (QAbstractEventDispatcher::instance() != nullptr)
{
qDebug() << "An existing event loop is running, exiting it.";
QCoreApplication::exit(); // End the existing event loop if running
}
int argc = 0;
char **argv = nullptr;
qDebug() << "In child process B1";
QApplication childApp(argc, argv);
qDebug() << "In child process B2";
QPushButton *button = new QPushButton("Terminate Child");
qDebug() << "In child process C";
QObject::connect(button, &QPushButton::clicked, &childApp, &QApplication::quit);
qDebug() << "In child process D";
QTimer::singleShot(100, [button]() {
qDebug() << "In child process E";
button->show();
qDebug() << "In child process F";
});
qDebug() << "In child process G";
childApp.exec();
qDebug() << "In child process H";
_exit(0); // Ensure clean exit of the child process
}
else
{
qDebug() << "In parent process";
int status;
waitpid(pid, &status, 0);
}
}
Upvotes: 1
Views: 104