Index: CloudConnect/main.cpp =================================================================== diff -u -rb47dcc8b37ff07a7efec4dffd0ab1c6f63e5e614 -r59b4c22f45a1d098064a886452769204e90cfb4b --- CloudConnect/main.cpp (.../main.cpp) (revision b47dcc8b37ff07a7efec4dffd0ab1c6f63e5e614) +++ CloudConnect/main.cpp (.../main.cpp) (revision 59b4c22f45a1d098064a886452769204e90cfb4b) @@ -15,7 +15,7 @@ int main(int argc, char *argv[]) { - // Block SIGINT and SIGTERM from normal delivery; redirect them to a fd. + // Block SIGINT and SIGTERM from normal delivery sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); @@ -32,12 +32,11 @@ app.setApplicationName("CloudConnect"); app.setApplicationVersion("1.0"); - // Notifier runs on the main thread inside the event loop — safe to call quit(). + // notifier runs on the main thread inside the event loop QSocketNotifier notifier(sfd, QSocketNotifier::Read); QObject::connect(¬ifier, &QSocketNotifier::activated, [&](int fd) { struct signalfd_siginfo info{}; - // signalfd delivers whole signalfd_siginfo records; a short read leaves - // info zeroed, so quitting on one would report no signal at all. + // A short read leaves info zeroed; don't quit on a phantom signal. const ssize_t bytes = read(fd, &info, sizeof(info)); if (bytes != sizeof(info)) { qWarning("Short read from signalfd (%zd bytes); ignoring", bytes); @@ -59,21 +58,49 @@ QCommandLineOption configOption( {"c", "config"}, "Path to the configuration INI file.", "config", - QDir(app.applicationDirPath()).filePath("config/CloudConnect.ini") + QDir(app.applicationDirPath()).filePath("/home/leahi/Public/leahi-realtime-cdt/CloudConnect/config/CloudConnect.ini") ); QCommandLineOption msgHandlingOption( {"m", "msg_handling"}, "Path to the message handling INI file.", "msg_handling", - QDir(app.applicationDirPath()).filePath("config/LeahiMsgHandling.ini") + QDir(app.applicationDirPath()).filePath("/home/leahi/Public/leahi-realtime-cdt/CloudConnect/config/LeahiMsgHandling.ini") ); parser.addOption(configOption); parser.addOption(msgHandlingOption); parser.process(app); - CloudConnectController ccController(parser.value(configOption), - parser.value(msgHandlingOption)); - if (!ccController.listenForApp()) { - return 1; - } + QThread controllerThread; + CloudConnectController ccController(parser.value(configOption), parser.value(msgHandlingOption)); - return app.exec(); + // Bind on the controller thread: QLocalServer's socket engine belongs to the + // thread that calls listen(), and accepted sockets are parented under it. + QObject::connect(&controllerThread, &QThread::started, &ccController, + [&ccController, &app]() { + if (!ccController.listenForApp()) { + qCritical() << "Failed to bind the app socket; shutting down"; + QMetaObject::invokeMethod(&app, [&app]() { app.exit(1); }, Qt::QueuedConnection); + return; + } + // QTcpSocket belongs to the thread that constructs it. + // Broker connect is async, so false = config failure, not unreachable. + if (!ccController.connectToCloud()) { + qCritical() << "Failed to start the MQTT connection; shutting down"; + QMetaObject::invokeMethod(&app, [&app]() { app.exit(1); }, Qt::QueuedConnection); + return; + } + // CAN last; frames arriving before MQTT CONNACK are still dropped. + if (!ccController.startCan()) { + qWarning() << "Failed to start the CAN interface; no frames will be forwarded"; + } + } + ); + + ccController.initThread(controllerThread); + + const int rc = app.exec(); + + // Stop the worker thread before the controller is destroyed at scope exit. + controllerThread.quit(); + controllerThread.wait(); + + return rc; }