Skip to content

Refactor Connect/Disconnect out to CClient with an explicit connection state - #3805

Merged
ann0see merged 8 commits into
jamulussoftware:mainfrom
mcfnord:client-connection-state
Aug 13, 2026
Merged

Refactor Connect/Disconnect out to CClient with an explicit connection state#3805
ann0see merged 8 commits into
jamulussoftware:mainfrom
mcfnord:client-connection-state

Conversation

@mcfnord

@mcfnord mcfnord commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Short description of changes

This picks up and finishes #3372 (@ann0see's extract of @pgScorpio's work in #2550), which had stalled on review discussion. It contains two commits:

  1. The original Refactor Connect and Disconnect functionality out to CClient #3372 refactor, ported onto current main (authorship preserved): Connect() / Disconnect() move out of CClientDlg into CClient, which emits signals the dialog consumes. CClientDlg no longer holds connection logic — it only reacts (OnConnecting, OnConnectingFailed, OnDisconnect). Connect-on-startup moves out of the dialog constructor into main.cpp via Client.Connect(...), so it now also works identically in --nogui mode.

  2. An explicit connection state machine, addressing the state-model review comments in Refactor Connect and Disconnect functionality out to CClient #3372: a new EConnectionState (CS_DISCONNECTED / CS_CONNECTING / CS_CONNECTED) owned by CClient as the single source of truth, with every transition emitting ConnectionStateChanged. Following that discussion's definitions: a connection is requested (CS_CONNECTING) when the audio stream starts toward the configured server, and established (CS_CONNECTED) once the server assigns our channel ID. Accordingly, the signal formerly emitted from Start() as Connected(name) is renamed Connecting(name), since at that point the connection is only requested. CClient::Connect() now terminates any current connection first, so connecting while connected behaves as a clean reconnect.

This is a step toward #3801 (CClient as the orchestration layer behind UI and JSON-RPC): a follow-up PR stacked on this one exposes connect/disconnect/state over JSON-RPC as symmetric consumers of the same signals.

Context: Fixes an issue?

Fixes #3367. Supersedes #3372 (both commits build on it; the first preserves its authorship).

Does this change need documentation? What needs to be documented and how?

No user-facing behavior change. The follow-up JSON-RPC PR updates docs/JSON-RPC.md.

Status of this Pull Request

Ready for review.

What is missing until this pull request can be merged?

Review.

Tested: full GUI build and CONFIG+=headless build on Linux/Qt 5.15; a --nogui client was driven through connect → reconnect-while-connected → disconnect cycles against a local server, observing the Connecting/ConnectionStateChanged/Disconnected signal sequence via the follow-up PR's JSON-RPC notifications (16/16 scripted checks pass); clean SIGTERM shutdown verified.

Checklist

  • I've verified that this Pull Request follows the general code principles
  • I tested my code and it does what I want
  • My code follows the style guide
  • I waited some time after this Pull Request was opened and all GitHub checks completed without errors.
  • I've filled all the content above

AUTOBUILD: Please build all targets

🤖 Generated with Claude Code

@mcfnord
mcfnord marked this pull request as ready for review July 19, 2026 02:25
@ann0see

ann0see commented Jul 19, 2026

Copy link
Copy Markdown
Member

Thanks. This needs thorough testing as this refactor is definitely non trivial.

@ann0see
ann0see requested review from pljones and softins July 19, 2026 05:56
@pljones

pljones commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

I'm just wondering if we should cut a 4.0.0 off where we are on main plus the TCP changes when they land and have these refactorings land onto 4.1.0 (4.0.0dev)?

We're currently holding up a feature (raw audio) that benefits all users for work that has infrastructure value.

It's definitely good to have them, though.

@ann0see

ann0see commented Jul 19, 2026

Copy link
Copy Markdown
Member

We're currently holding up a feature (raw audio) that benefits all users for work that has infrastructure value.

It's stalled for a good reason though. Now we know it works (but probably would benefit from PLC) but at the start we didn't.

@ann0see

ann0see commented Jul 19, 2026

Copy link
Copy Markdown
Member

GUI tested and I can confirm it works as expected. From the code review it also looks fine. I'll still need to do an in depth analysis though.

Comment thread src/client.cpp
Comment thread src/client.cpp
Comment thread src/client.h Outdated
Comment thread src/client.h Outdated
Comment thread src/clientdlg.cpp
Comment thread src/util.h Outdated

@ann0see ann0see left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if this breaks feedback detection somehow. (Manually).

Free model from open code (Big Pickle) had the following comments (also seem my comments on this)

This one I was already suspicious about:

Bug Assessment
Likely Bug: Stop() emits Disconnected unconditionally
CClient::Stop() now always emits Disconnected() even if the client wasn't connected:
void CClient::Stop()
{
    // ... cleanup ...
    SetConnectionState ( CS_DISCONNECTED ); // no-op if already disconnected
    emit Disconnected();                    // <-- ALWAYS emitted
}
This means every Stop() call triggers the full UI teardown chain in OnDisconnect() (reset button text, hide mixer board, clear faders, reset deco). If Stop() is ever called when the client wasn't running, the UI will react to a spurious disconnect. The old code guarded this with if (IsRunning()).

Potential Issue: bDetectFeedback not reset on disconnect
When OnConnecting() starts the feedback detection timer and sets bDetectFeedback = true, neither OnDisconnect() nor Stop() resets it. The single-shot timer will expire, but the flag remains stale. Not a crash bug, but could cause unexpected behavior if bDetectFeedback is checked elsewhere before the next connection.

Fair point. Maybe be problematic - but not sure...

Minor: CS_CONNECTING state during Connect() failure path
In Connect(), the catch block calls Stop(), which transitions CS_CONNECTING → CS_DISCONNECTED and emits Disconnected. Then the catch block also emits ConnectingFailed. So the UI gets both signals in sequence — this works, but ConnectingFailed arrives after the UI has already been torn down by Disconnected, which could be confusing for error display ordering.

Probably really not too bad.

@pljones pljones added this to Tracking Jul 20, 2026
@pljones pljones added this to the Release 4.1.0 milestone Jul 20, 2026
@github-project-automation github-project-automation Bot moved this to Triage in Tracking Jul 20, 2026
@github-project-automation github-project-automation Bot moved this from Triage to Waiting externally in Tracking Jul 20, 2026
@pljones pljones moved this from Waiting externally to Backlog in Tracking Jul 20, 2026
mcfnord pushed a commit to mcfnord/jamulus that referenced this pull request Jul 21, 2026
- SIGTERM/SIGINT: route through Disconnect() instead of a raw Stop(),
  and guard Disconnect() on the connection state rather than IsRunning()
  (which tracks the audio device). IsRunning() is false while connecting
  and in headless mode, so the old guard could skip notifying the server
  on shutdown; the raw Stop() worked around that but also fired a
  spurious disconnect when idle. Now the server is notified iff a
  connection is pending or established, via the single Disconnect() path.
- Move EConnectionState from util.h to client.h (it is client specific).
- Add a doc-comment to SetConnectionState; reword two member comments.

Addresses review feedback on jamulussoftware#3805.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ann0see

ann0see commented Jul 21, 2026

Copy link
Copy Markdown
Member

I'll look at this later. I'd like to have this in 4.0 not 4.1 as it's crucial for clean connect/disconnect RPC functionality.

Comment thread src/client.cpp
@ann0see ann0see added the AI AI generated or potentially AI generated label Jul 21, 2026
@mcfnord mcfnord mentioned this pull request Jul 22, 2026
5 tasks
@pljones pljones modified the milestones: Release 4.1.0, Release 4.0.0 Jul 27, 2026
@pljones pljones moved this from Waiting on Team to Waiting externally in Tracking Aug 2, 2026
@ann0see

ann0see commented Aug 9, 2026

Copy link
Copy Markdown
Member

@mcfnord Please have a look at this Pr again.

mcfnord pushed a commit to mcfnord/jamulus that referenced this pull request Aug 9, 2026
softins noted several QString value parameters that can be const refs.
Qt handles const refs across the signal/slot boundary, copying only when
an argument has to cross a thread, so this removes copies without
changing behaviour.

Connect(), SetConnectedServerName(), and the Connecting()/ConnectingFailed()
signals now take const QString&. The receiving slots already declared
const QString&, so the signatures match exactly rather than relying on
conversion.

Addresses review feedback on jamulussoftware#3805.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mcfnord

mcfnord commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

🤖 AI

All four suggestions are in, as one commit. The receiving slots already declared const QString&, so signal and slot signatures now match exactly instead of relying on conversion.

Checked while making the change: Connect() calls Disconnect() before it reads strServerName, and nothing on that path writes strConnectedServerName, so the reference stays valid. All three call sites pass function-local strings.

GUI and headless both build clean. A headless client still connects to a local server end to end: jamulusclient/getClientInfo returns {"connected": true}.


🤖 This message was written by AI and reviewed by @mcfnord.

@softins softins left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me now. Thanks!

@softins
softins requested a review from ann0see August 9, 2026 21:01
@ann0see

ann0see commented Aug 10, 2026

Copy link
Copy Markdown
Member

Please don't merge. Updating the branch = rebase.

Comment thread src/client.cpp
}
catch ( const CGenErr& )
{
// a dead audio backend (e.g. JACK was shut down) must not prevent the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Maybe the warning should be logged.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI: Logged in 92d32f0. The catch takes generr again and the error text goes to qWarning() before the disconnect continues. The wording and the log level are easy to change if you want something different.

@ann0see ann0see left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above comment.

@ann0see
ann0see requested a review from pljones August 10, 2026 08:07

@ann0see ann0see left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise it seems good.

Comment thread src/client.cpp
Comment thread src/client.cpp
@softins

softins commented Aug 12, 2026

Copy link
Copy Markdown
Member

@mcfnord this rebases cleanly on to the latest main. Shall I push my rebased branch, or leave it to you?

@ann0see

ann0see commented Aug 12, 2026

Copy link
Copy Markdown
Member

Cleaner if he does it.

pgScorpio and others added 8 commits August 12, 2026 22:32
This is an extract from jamulussoftware#2550
Co-authored-by: ann0see <20726856+ann0see@users.noreply.github.com>
Introduce EConnectionState (disconnected / connecting / connected) owned
by CClient as the single source of truth. A connection is 'requested'
when the audio stream starts (CS_CONNECTING) and 'established' once the
server assigns our channel ID (CS_CONNECTED). Every transition emits
ConnectionStateChanged.

Rename the Connected(name) signal emitted from Start() to
Connecting(name), since at that point the connection is only requested,
not established. CClient::Connect() now terminates any current
connection first, so connecting while connected behaves as a reconnect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SIGTERM/SIGINT: route through Disconnect() instead of a raw Stop(),
  and guard Disconnect() on the connection state rather than IsRunning()
  (which tracks the audio device). IsRunning() is false while connecting
  and in headless mode, so the old guard could skip notifying the server
  on shutdown; the raw Stop() worked around that but also fired a
  spurious disconnect when idle. Now the server is notified iff a
  connection is pending or established, via the single Disconnect() path.
- Move EConnectionState from util.h to client.h (it is client specific).
- Add a doc-comment to SetConnectionState; reword two member comments.

Addresses review feedback on jamulussoftware#3805.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pljones noted the comment explaining why QCoreApplication::instance()->exit()
is called here (to trigger OnAboutToQuit) should have stayed through the
Disconnect()-guard refactor. Restoring it.
softins noted several QString value parameters that can be const refs.
Qt handles const refs across the signal/slot boundary, copying only when
an argument has to cross a thread, so this removes copies without
changing behaviour.

Connect(), SetConnectedServerName(), and the Connecting()/ConnectingFailed()
signals now take const QString&. The receiving slots already declared
const QString&, so the signatures match exactly rather than relying on
conversion.

Addresses review feedback on jamulussoftware#3805.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CClient::Stop() calls Init(), which throws from CSound::Init() when JACK
has been shut down - before CreateCLDisconnection() runs. On SIGTERM after
JACK death the exception escaped the Qt slot and the server was never
notified, holding the dead client's slot until the ~30 s timeout. Catch
the error so the disconnect message is always sent; the server then frees
the slot immediately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
softins noted that the other restricted methods in CClient are protected
rather than private, and tested that both Start() and Stop() compile as
protected. Moving the declarations rather than making them private keeps
them consistent with Init(), ProcessSndCrdAudioData() and the rest of the
restricted set, and forces external callers through Connect()/Disconnect().

A full client build (Qt 5.15, JACK enabled) is clean, so no caller outside
the class remains. A translation unit that calls p->Start() on a CClient*
is now rejected: "'void CClient::Start()' is protected within this context".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The catch around Init() in Stop() exists so a dead audio backend cannot
stop the disconnect message reaching the server, but it discarded the
reason silently. ann0see asked for the warning to be logged; the error
text now goes to qWarning() while the disconnect still proceeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mcfnord
mcfnord force-pushed the client-connection-state branch from 92d32f0 to 5fb6932 Compare August 12, 2026 22:32
@github-project-automation github-project-automation Bot moved this from Waiting externally to Waiting on Team in Tracking Aug 13, 2026
@ann0see
ann0see merged commit be8a775 into jamulussoftware:main Aug 13, 2026
15 checks passed
@github-project-automation github-project-automation Bot moved this from Waiting on Team to Done in Tracking Aug 13, 2026
@ann0see

ann0see commented Aug 13, 2026

Copy link
Copy Markdown
Member

Thanks. This was an important one... I'd say that once we have the JSON-RPC ones in we should tag a new beta. This needs some real world testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI AI generated or potentially AI generated refactoring Non-behavioural changes, Code cleanup

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Refactor Connect() and Disconnect() functions in GUI to allow JSON RPC to update UI

5 participants