#include "SingleInstance.h" #include #include #include namespace PhpQml::Bridge { namespace { constexpr int kForwardConnectTimeoutMs = 200; constexpr int kForwardWriteTimeoutMs = 1500; constexpr int kBindRetries = 3; constexpr int kBindRetryDelayMs = 50; } // namespace SingleInstance::SingleInstance(const QString& applicationId, QObject* parent) : QObject(parent) , m_appId(applicationId) { connect(&m_server, &QLocalServer::newConnection, this, &SingleInstance::onNewConnection); } QString SingleInstance::endpointName() const { // QLocalServer maps the name to a per-user path on Unix and a named // pipe on Windows. The appId keeps multiple bridge-using apps apart. return QStringLiteral("php-qml-bridge.%1").arg(m_appId); } bool SingleInstance::acquireOrForward(const QStringList& launchArgs) { const QString name = endpointName(); for (int attempt = 0; attempt < kBindRetries; ++attempt) { if (m_server.listen(name)) { return true; // we are the live instance } // Bind failed. Either a real instance is running (forward args), or // a stale socket/pipe is left over from a crashed process (clean up // and retry). Distinguish by trying to connect. QLocalSocket probe; probe.connectToServer(name); if (probe.waitForConnected(kForwardConnectTimeoutMs)) { QDataStream out(&probe); out.setVersion(QDataStream::Qt_6_5); out << launchArgs; probe.flush(); probe.waitForBytesWritten(kForwardWriteTimeoutMs); probe.disconnectFromServer(); return false; } // No live peer responding — likely stale socket. Remove and retry // with a brief backoff (PLAN.md §3 *Edge cases — Single-instance // launch race*). QLocalServer::removeServer(name); QThread::msleep(kBindRetryDelayMs); } // Exhausted retries without binding and without a live peer. Better to // act as the live instance than to deadlock both processes into exiting. return true; } void SingleInstance::onNewConnection() { while (auto* socket = m_server.nextPendingConnection()) { connect(socket, &QLocalSocket::readyRead, this, [this, socket]() { QDataStream in(socket); in.setVersion(QDataStream::Qt_6_5); QStringList args; in >> args; if (in.status() == QDataStream::Ok) { emit launchArgsReceived(args); } }); connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater); } } } // namespace PhpQml::Bridge