#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(); // Probe first: if a live instance answers, forward and exit. // This avoids a race where eagerly calling removeServer() would break // a running peer. { 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 answered. The endpoint may not exist, or a stale // file is left over from a crashed process โ€” removeServer() handles // both cases safely. QLocalServer::removeServer(name); for (int attempt = 0; attempt < kBindRetries; ++attempt) { if (m_server.listen(name)) { return true; } QThread::msleep(kBindRetryDelayMs); } // Exhausted retries โ€” better to continue as a degraded "live" instance // than to deadlock-exit (PLAN.md ยง3 *Edge cases โ€” Single-instance // launch race*). Subsequent invocations may not be forwarded, but // this process will still run. 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