diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-05-13 21:32:28 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-05-13 21:32:28 -0500 |
| commit | 8b4f9596d3e2b437317418b0af1aad68db056ee2 (patch) | |
| tree | a9d3dd58eb047053b9e71b0d5a1ee290650d6150 | |
| parent | 12de3c95f155213206bfa3fdc0f680c92c55a59f (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.cpp | 41 |
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;
+ }
}
}
}
|
