aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-08 17:33:00 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-08 17:33:00 -0500
commit227be23a0c141690bc7c27dafb9bcdb67f37075a (patch)
treeaf5314e2dc4ec9bd7511c3cb93a146031d3e91f9
parent6391164071b859e901283bec098974ef6b104f50 (diff)
multiprocess: recover from stale SysV shm + unix socket after crash
Linux QSharedMemory uses SysV segments that survive process crashes. On the next launch, m_SharedMem.create() fails with AlreadyExists and the constructor would either treat the corpse as a live primary (blocking the user behind a ghost) or throw. The unix socket file is similarly left behind and would make QLocalServer::listen() fail with AddressInUseError if reclaim succeeded. Now: probe the primary's listener via QLocalSocket::connectToServer before believing the segment is alive. If the probe fails, detach and re-create. If create then fails (another attached client keeps refcount > 0), re-attach so the constructor returns in a usable secondary state instead of silently no-op'ing every later sendMessage. Also call QLocalServer::removeServer() before listen() to clear the orphaned socket file when we did successfully reclaim. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-rw-r--r--src/src/multiprocess.cpp61
1 files changed, 58 insertions, 3 deletions
diff --git a/src/src/multiprocess.cpp b/src/src/multiprocess.cpp
index 8a15fc3..b22ada3 100644
--- a/src/src/multiprocess.cpp
+++ b/src/src/multiprocess.cpp
@@ -11,8 +11,22 @@ static const int s_Timeout = 5000;
using MOBase::reportError;
+// Liveness probe: a real primary owns a QLocalServer listening on s_Key.
+// If we can connect, a primary exists. If not, the shm/socket is stale
+// (prior crash left SysV segment + unix socket file behind on Linux).
+static bool primaryAlive()
+{
+ QLocalSocket probe;
+ probe.connectToServer(s_Key, QIODevice::WriteOnly);
+ const bool alive = probe.waitForConnected(500);
+ if (alive) {
+ probe.disconnectFromServer();
+ }
+ return alive;
+}
+
MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject* parent)
- : QObject(parent)
+ : QObject(parent)
{
m_SharedMem.setKey(s_Key);
@@ -24,7 +38,42 @@ MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject* parent)
if (error == QSharedMemory::AlreadyExists && !allowMultiple) {
// Primary instance likely exists. Try to attach as ephemeral process.
if (m_SharedMem.attach()) {
- m_Ephemeral = true;
+ // Verify a primary is actually listening. Stale SysV shm on Linux
+ // survives crashes — attach succeeds against a dead segment.
+ if (primaryAlive()) {
+ m_Ephemeral = true;
+ } else {
+ MOBase::log::debug(
+ "stale shared memory detected (no primary listener), "
+ "claiming ownership");
+ m_SharedMem.detach();
+ if (m_SharedMem.create(1)) {
+ m_OwnsSM = true;
+ error = QSharedMemory::NoError;
+ } else {
+ // Another attached client keeps refcount > 0; on Linux Qt only
+ // frees the segment when the last attacher detaches. Re-attach
+ // as a secondary so the constructor returns in a usable state
+ // and sendMessage() can still try to deliver the request via
+ // the unix socket — it would otherwise leave both ownership
+ // flags clear and silently no-op every later call.
+ const QString reclaimError = m_SharedMem.errorString();
+ if (m_SharedMem.attach()) {
+ MOBase::log::warn(
+ "could not reclaim stale shared memory ({}), "
+ "running as secondary",
+ reclaimError);
+ m_Ephemeral = true;
+ error = QSharedMemory::NoError;
+ } else {
+ MOBase::log::warn(
+ "could not reclaim or re-attach stale shared memory "
+ "(reclaim: {}, reattach: {})",
+ reclaimError, m_SharedMem.errorString());
+ error = m_SharedMem.error();
+ }
+ }
+ }
} else {
// Handle races with stale shared memory state:
// between create() and attach(), the owner may have disappeared.
@@ -60,9 +109,15 @@ MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject* parent)
if (m_OwnsSM) {
connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()),
Qt::QueuedConnection);
+ // Clear any stale unix socket file from a prior crashed primary,
+ // otherwise listen() fails with AddressInUseError on Linux.
+ QLocalServer::removeServer(s_Key);
// has to be called before listen
m_Server.setSocketOptions(QLocalServer::WorldAccessOption);
- m_Server.listen(s_Key);
+ if (!m_Server.listen(s_Key)) {
+ MOBase::log::warn("QLocalServer listen failed: {}",
+ m_Server.errorString().toStdString());
+ }
}
}