Skip to content

fix: v26.10.6's missing x86_64 AppImage, two race fixes, sanitizer lanes, and HackRF unplug + Windows (#42, #46, #47, #75, #80) - #78

Merged
W5TSU merged 8 commits into
mainfrom
fix/open-issues
Oct 20, 2026
Merged

W5TSU merged 8 commits into
mainfrom
fix/open-issues

Conversation

@W5TSU

@W5TSU W5TSU commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Closes #75, closes #46, closes #47, closes #80, closes #42.

All four open issues, plus the broken v26.10.6 x86_64 AppImage.

v26.10.6 shipped without an x86_64 AppImage

HackRfDiscovery.cpp used BOARD_ID_UNDETECTED, which Ubuntu 22.04's libhackrf 2021.03.1 (the x86_64 AppImage's build host) calls BOARD_ID_INVALID. Only the tag build uses that header, so only the release broke, and release signing timed out waiting for the missing file. It now uses the number, 0xFF. A new ctest, libhackrf_api_floor, fails when src/ uses a libhackrf name the 2021.03.1 header doesn't declare. This is the second break of this kind (#67).

#75: the demo radio sent a frame after "disconnected"

RadioModel disconnects the simulator's vended connection directly, and SimBackend split that into two queued slots: re-emit disconnected(), then close the gate. A spectrum row could land between them. One handler now closes the gate and then announces the disconnect, the core of upstream aethersdr#6088.

#47: ASan lane

  • Fail-safe unkey held until the next tick (Icom). sendUserCommand() read the clock once and queueWrite() stamped the request with a later reading, so the pump skipped the PTT-off as not yet due whenever a millisecond boundary fell between the two. This failed 2 of 40 local runs. It now pumps with a fresh reading. A clock seam reproduces the race on every run: the new check failed before the fix, and 40 of 40 runs pass after it.
  • Out of disk. The build had failed since 2026-10-03, with every test linking its own static copy of the core. A new AETHER_SHARED_CORE option (sanitizer CI only) builds the core as one shared library, from upstream ci(sanitizers): link the ASan lane against a shared core so it fits on disk. Principle VIII. aethersdr/AetherSDR#6136. A failed Configure or Build now reaches the tracking issue instead of skipping the whole report chain.

#46: TSan lane

weather_radar_loading_test timed out every week, with a 30 s limit against ~15 s plain and 26 s under ASan. In instrumented builds every TIMEOUT is now ×4 (upstream aethersdr#6208); ordinary builds keep their limits.

#80: TSan on Qt 6.12

The TSan image moved from Qt 6.8.3 to 6.12.0, and four tests (tci_automation, tci_server_review, hl2_tci_signaling, whats_new_dialog) now fail on two reports wholly inside Qt: a lock-order inversion in its TLS backend init, and ~QHttpThreadDelegate racing a main-thread reply delete. Upstream suppressed exactly these, anchored on the Qt frames, in aethersdr#6173; tsan.supp takes those entries verbatim.

#42: HackRF backend, the last two items

  • Unplug mid-session. HackRfWorker::streamStopped was declared and handled but never emitted. An unplugged HackRF froze the spectrum, stayed "connected", and refused a reconnect. The backend's 50 ms tick now runs checkStreamHealth(): if libhackrf says the stream ended, or no transfer arrives for 2 s, the backend reports "HackRF stream stopped unexpectedly" and disconnects, in that order (rule 6).
  • Windows. scripts/setup/setup-hackrf.ps1 builds libhackrf 2026.01.3 with MSVC against vcpkg's libusb and pthreads, as Great Scott Gadgets' guide does. The installer and portable ZIP ship hackrf.dll, libusb-1.0.dll and pthreadVC3.dll, and REQUIRE_HACKRF=ON gates both the installer and the per-PR Windows build. The firmware asks Windows for WinUSB, so there's no Zadig step.
  • Multi-slice receive, the one other v1 goal in HackRF backend — highly experimental (RX+TX, satellite FM/CW) #42's design, isn't in this PR. It moves to HackRF: multi-slice receive across the wide capture #79.

Testing

  • Whole tree with AETHER_SHARED_CORE=ON: builds, and 512/512 ctest pass.

  • A -fsanitize=thread configure scales weather_radar_loading_test to 120 s; a plain configure leaves it at 30 s.

  • Every HackRF source compiles against the libhackrf 2021.03.1 header.

  • Unplug, on hardware (HackRF Pro). tools/hackrf_unplug_test.py (new, receive only) resets the board with libhackrf's reset request, so it drops off USB and re-enumerates. It passed 9/9:

    • disconnected in 0.3–0.4 s;
    • still running;
    • reconnected and receiving again.

    On the old build the app never disconnected and refused to reconnect.

  • Red then green: sim_backend_test (2 checks), icom_identity_test (1), hackrf_backend_test (2 ordering checks).

  • Windows: CI only, not yet run against a HackRF on Windows. The Windows Installer and AppImage workflows are dispatched on this branch before merge.

🤖 Generated with Claude Code

W5TSU and others added 8 commits October 19, 2026 12:53
… API floor

The v26.10.6 tag built every package but the x86_64 AppImage, which failed
to compile: HackRfDiscovery.cpp used BOARD_ID_UNDETECTED, a name newer
libhackrf headers declare but Ubuntu 22.04's libhackrf-dev 2021.03.1 (the
AppImage's build host) calls BOARD_ID_INVALID. Every other build has the
newer header, so only the release tag saw it. Use the number, 0xFF, as
HackRfWorker::readBoardId already does.

No per-PR lane builds against that header, and this is the second break of
this class (#67), so add libhackrf_api_floor: a ctest source scan that fails
when src/ uses a libhackrf name outside the 2021.03.1 header's declarations.
Checked against the 2021.03.1 header itself: every HackRF source compiles
with it after this change, and the scan flags exactly the two lines that
broke the tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full suite failed once on main with "sim: nothing emitted after
disconnected() (spectrumFrameReady)" in backend_seam_affinity_test.
RadioModel disconnects the simulator's vended RadioConnection directly, and
SimBackend turned that into two queued slots: a signal-to-signal re-emit of
disconnected(), then the handler that clears m_connected and stops the
worker. A spectrum row the worker queued between the two was forwarded after
the announcement, breaking IRadioBackend rule 6.

One handler now closes the gate and then emits disconnected(), so there is
no gap for a row to land in. The core of upstream aethersdr#6088; its session stamp
on spectrum rows depends on refactors this fork does not have.

sim_backend_test pins the ordering: at the moment disconnected() is
announced the backend already reports disconnected, and a row emitted from
inside that announcement is dropped. Both checks failed before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
icom_identity_test failed intermittently ("ambiguity releases the previously
selected destination without retargeting unkey"): once under ASan on CI and
2 runs in 40 in a plain local Debug build. sendUserCommand() read the clock
once, queueWrite() stamped the request with a second, later reading, and the
pump at the first reading skipped the request as not yet due. When a
millisecond boundary fell between the two reads, the PTT-off write sat in
the queue until the next timer tick instead of leaving at once.

Pump with a fresh reading. The disconnect path already reads the clock after
queueing its emergency writes; the meter tick's reads are routine and only
wait one tick.

A test seam, m_nowForTest, lets the test make every clock reading later than
the last, which reproduces the race on every run: the new check failed
deterministically before the fix and passes after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t timeouts under sanitizers (#46, #47)

Both weekly sanitizer lanes have been red for weeks without finding a
sanitizer report:

- ASan + UBSan (#47) stopped building on 2026-10-03: "final link failed:
  No space left on device". Every test executable linked its own static
  copy of libaethercore. A new AETHER_SHARED_CORE option (default OFF,
  never for releases) builds the core as one shared library, and the ASan
  lane turns it on. From upstream aethersdr#6136, with its workflow changes: df -h
  around the build, and a Configure or Build failure now reaches the
  tracking issue instead of skipping the whole report chain. Static checks
  assert per PR that the option yields libaethercore.so and the default
  stays static.
- TSan (#46) timed out weather_radar_loading_test every week. It takes ~15 s
  in a plain build (mostly waiting), 26 s under ASan, and over its 30 s
  limit under TSan. In an instrumented build every test TIMEOUT, its own or
  the 300 s default, is now multiplied by AETHER_SANITIZER_TIMEOUT_SCALE
  (4). Ordinary builds keep every limit as written. From upstream aethersdr#6208.

Verified locally: the whole tree (app and all 512 tests) builds with
AETHER_SHARED_CORE=ON and every test passes; a configure with
-fsanitize=thread scales weather_radar_loading_test to 120 s and a plain
configure leaves it at 30 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HackRfWorker declared streamStopped() and HackRfBackend handled it, but
nothing ever emitted it. libhackrf reports a stream that dies under it
(unplug, USB error, reset) only by calling back no more, so the spectrum
froze, the session stayed "connected", and connecting again was refused
with "already connected to a radio".

The backend's 50 ms arbiter tick now also runs
HackRfWorker::checkStreamHealth(). A stream that should be running but
that libhackrf says has ended (hackrf_is_streaming), or that has delivered
no transfer for 2 s, is marked stopped and reported once. The backend then
reports "HackRF stream stopped unexpectedly" and disconnects, in that
order: the old handler disconnected first and emitted the error after
disconnected(), which IRadioBackend rule 6 forbids. A report after the
session has ended is ignored.

hackrf_backend_test pins the rule (streamFault) and the ordering; the
ordering checks failed on the old handler. tools/hackrf_unplug_test.py
checks it on hardware, receive only: it resets the board with libhackrf's
own reset request, so it drops off USB and re-enumerates the way an unplug
does (or waits for a real unplug with --physical). On a HackRF Pro it
passes 9/9 (disconnected in 0.3-0.4 s, still running, reconnects, receives
again); on the old build the app never disconnected and could not
reconnect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every Windows build so far compiled the HackRF backend out: nothing supplied
libhackrf, which ships no Windows binaries. scripts/setup/setup-hackrf.ps1
builds it the way Great Scott Gadgets' own Windows guide does, with MSVC
against vcpkg's libusb and pthreads, at the release the macOS DMG builds
(2026.01.3, from its .zip; the host/libhackrf tree is identical to the
.tar.xz the DMG pins). It stages hackrf.h, hackrf.lib and the hackrf,
libusb and pthreads DLLs in third_party/hackrf.

- CMakeLists.txt: on Windows, link that staging as an imported DLL and copy
  its DLLs next to AetherSDR.exe. The probes now chain on HACKRF_TARGET
  rather than HACKRF_FOUND, which pkg_check_modules caches: keyed on the
  flag, a reconfigure skipped the probe that sets the target and the core
  built without libhackrf's include path.
- windows-installer.yml: build it (cached on the script), configure with
  REQUIRE_HACKRF=ON, and ship the three DLLs in the payload, where
  check-deploy-dependencies.ps1 already proves every import is present.
- ci.yml check-windows: the same build and REQUIRE_HACKRF=ON, so every PR
  compiles the backend with MSVC.
- README: an optional setup step in the Windows build instructions.
  docs/HACKRF.md: a Windows section (no Zadig: the firmware asks Windows for
  WinUSB). Not yet checked against a HackRF on a Windows machine, and the
  doc says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry (#42)

libhackrf built and staged on both Windows lanes; the step then failed on
its last line, removing third_party\hackrf-temp, because something still
held a handle on build\src a moment after the post-link DLL copy. The
scratch tree is now removed with a short retry and only a warning if it
stays busy, and Expand-Archive overwrites a leftover one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rts (#80)

The TSan image now carries Qt 6.12.0 (it was 6.8.3 on the last weekly
run), and four tests fail on two reports that are wholly inside Qt:
tci_automation, tci_server_review and hl2_tci_signaling on the lock-order
inversion between QSslSocketPrivate::tlsBackendInUse() and the TLS
factory-loader mutex, and whats_new_dialog on ~QHttpThreadDelegate vs a
main-thread reply delete. Upstream suppressed exactly these two, anchored
on the Qt frames, in aethersdr#6173; this takes its tsan.supp
entries verbatim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@W5TSU W5TSU changed the title fix: v26.10.6's missing x86_64 AppImage, two race fixes, sanitizer lanes, and HackRF unplug + Windows (#42, #46, #47, #75) fix: v26.10.6's missing x86_64 AppImage, two race fixes, sanitizer lanes, and HackRF unplug + Windows (#42, #46, #47, #75, #80) Oct 20, 2026
@W5TSU
W5TSU merged commit 21cf725 into main Oct 20, 2026
11 checks passed
@W5TSU
W5TSU deleted the fix/open-issues branch October 20, 2026 14:06
W5TSU added a commit that referenced this pull request Oct 20, 2026
… AppImage back (#81)

Version 26.10.7 in CMakeLists.txt, README.md, AGENTS.md and the AppStream metainfo; CHANGELOG section from #78; ROADMAP cycle heading, the HackRF highlight, and #79 for multi-slice receive.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant