aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-13 21:32:28 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-13 21:32:28 -0500
commit8b4f9596d3e2b437317418b0af1aad68db056ee2 (patch)
treea9d3dd58eb047053b9e71b0d5a1ee290650d6150
parent12de3c95f155213206bfa3fdc0f680c92c55a59f (diff)
multiprocess: claim primary listener when stale shm can't be reclaimed
Walked into this when a download manager click silently popped "An instance of Mod Organizer is already running" with no MO2 actually visible. State on disk: SysV shm segment from a prior MO2 still there with nattch=0 (kernel won't reap until IPC_RMID, which only the dead creator could call) plus the orphaned /tmp/mo-<key> socket file. The stale-recovery path was attaching, finding no listener via primaryAlive(), then trying to detach+create — which kept failing because the corpse segment still occupied the key — and falling all the way through to "re-attach as secondary". Result: the new launch flagged itself ephemeral, forwardToPrimary() had nothing to send, and the user got the ghost dialog. The shm is only advisory; the unix socket listener is the real lock. If primaryAlive() said no one's listening, we should run as primary even when we can't reclaim the orphaned segment. Also re-probe on listen() failure so a real race (another process becoming primary between our probe and our listen) demotes us cleanly instead of leaving us as a broken primary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-rw-r--r--src/src/multiprocess.cpp41
1 files changed, 20 insertions, 21 deletions
diff --git a/src/src/multiprocess.cpp b/src/src/multiprocess.cpp
index 3ac4c72..688e745 100644
--- a/src/src/multiprocess.cpp
+++ b/src/src/multiprocess.cpp
@@ -51,27 +51,17 @@ MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject* parent)
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();
- }
+ // SHM reclaim failed — the SysV segment is orphaned because the
+ // previous primary died without IPC_RMID, and only the creator
+ // can remove it. The unix socket is the real liveness signal
+ // and we already proved no listener exists, so claim primary
+ // anyway. The orphan segment is harmless once we own the socket.
+ MOBase::log::warn(
+ "stale shared memory could not be reclaimed ({}), "
+ "claiming primary via socket listener",
+ m_SharedMem.errorString());
+ m_OwnsSM = true;
+ error = QSharedMemory::NoError;
}
}
} else {
@@ -117,6 +107,15 @@ MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject* parent)
if (!m_Server.listen(s_Key)) {
MOBase::log::warn("QLocalServer listen failed: {}",
m_Server.errorString().toStdString());
+ // Another process may have started a listener between our probe and
+ // listen() (race during concurrent startup). Re-probe and demote
+ // ourselves to ephemeral if a real primary is now alive.
+ if (primaryAlive()) {
+ MOBase::log::info(
+ "another primary started concurrently, running as ephemeral");
+ m_OwnsSM = false;
+ m_Ephemeral = true;
+ }
}
}
}