aethersdr / aethersdr/AetherSDR

Floating-pan crash-loop guard can false-positive when two local instances share a settings store

Open
#4,901 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug GUI maintainer-review multi-pan priority: low
Dominant language
C++
Stars
221
Forks
117
Avg merge
2d 7h
Merged PRs (30d)
299

Description

Summary

The floating-panadapter crash-loop guard landed in #4863 (f6f56865) reads a persisted FloatingPanRestorePending marker at PanadapterStack construction. Finding it armed means "the previous process died mid-float", so the guard drops FloatingPanIds, comes up docked, and tells the operator:

N panadapter(s) restored docked — AetherSDR last closed unexpectedly while popping out. Pop out again to retry.

Two local instances sharing one settings store can make that sentence false.

Why two instances share a store

AppSettings::initGuiClientIdentity() (src/core/AppSettings.cpp) deliberately does not refuse a second local instance. It takes a QLockFile on the persistent GUI client ID and, on failure, falls back to a process-scoped ID with a warning rather than exiting. So a second instance is a supported configuration, and it reads and writes the same settings store.

The race

  1. Instance A pops out a panadapter. armFloatingRestoreMarker() writes FloatingPanRestorePending=True and save()s it before the reparent (this ordering is the whole point of #4863), then saveFloatingState() commits the pan ID.
  2. Within kFloatingRestoreSettleMs (5 s), instance B starts. Its PanadapterStack constructor reads the store, sees the marker armed and IDs present, and evaluates DropSavedIds.
  3. B blanks FloatingPanIds, retires the marker, and shows the operator a notice claiming the app last closed unexpectedly. Nothing crashed.

Severity

Low, and deliberately shipped as-is:

  • The window is the 5 s settle interval, and only if a second instance starts inside it.
  • This is not new state corruption — FloatingPanIds was already last-writer-wins across instances.
  • A's settle timer will clear the marker on schedule; A's own float is unaffected in-session.

What is new is the notice, which asserts something untrue about the user's last session. On a guard whose entire purpose is explaining an otherwise-silent behaviour change, a false explanation is the wrong failure.

Why the cheap fix is wrong

AppSettings::guiClientIdentityIsTransient() is the obvious gate — it is true precisely when another local process holds the lock. But it is also true whenever AETHER_AUTOMATION_* is set, because the automation identity path sets m_guiClientIdentityTransient = true unconditionally.

Gating the guard on it would therefore switch the guard off under automation — exactly where the pan float / pan dock verbs from #4864 would exercise this code path in CI. That trades a cosmetic bug for an untestable guard.

What a real fix looks like

Stamp the marker with the owning process identity instead of a bare True, and treat it as a previous-process crash only when that process is no longer alive. QLockFile already does same-machine PID-liveness (getLockInfo() / removeStaleLockFile()) and is already a dependency of the identity path, so a lock-file-shaped marker is likely cheaper and more portable than a hand-rolled kill(pid, 0) / OpenProcess split.

Worth weighing against simply narrowing the settle window, or dropping the notice's claim about why while keeping the state fix.

Pointers

  • src/gui/FloatingRestorePolicy.hevaluateFloatingRestore(), floatingRestoreWrites(), the key and value constants
  • src/gui/PanadapterStack.cpp — the constructor guard, armFloatingRestoreMarker(), clearFloatingRestoreMarker()
  • src/gui/MainWindow_Session.cpp — the operator notice
  • tests/floating_restore_policy_test.cpp — the pure-function test (note: in no ctest -R filter in ci.yml, so it does not gate merges)

Raised during review of #4863 and consciously deferred there rather than widening that PR. Original review thread: https://github.com/aethersdr/AetherSDR/pull/4863#discussion_r3744721904

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with evaluateFloatingRestore() and the marker constants in src/gui/FloatingRestorePolicy.h, then trace the constructor guard and marker helpers in src/gui/PanadapterStack.cpp. Review AppSettings::guiClientIdentityIsTransient() and QLockFile liveness behavior before choosing an approach. Update the pure-function coverage in tests/floating_restore_policy_test.cpp and verify the operator notice in src/gui/MainWindow_Session.cpp no longer makes a false crash claim.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.