Skip to content

client, test/suites: forward session timeout cause over websocket - #1519

Open
shardool-patil wants to merge 2 commits into
canonical:mainfrom
shardool-patil:fix/session-timeout-forwarding
Open

shardool-patil wants to merge 2 commits into
canonical:mainfrom
shardool-patil:fix/session-timeout-forwarding

Conversation

@shardool-patil

@shardool-patil shardool-patil commented Aug 21, 2026 •

Copy link
Copy Markdown

Description

When a trust establishment session expires, the client previously received a generic connection error (websocket: close 1006: unexpected EOF) instead of the actual cause (Session timeout exceeded)

Changes

  • client/websocket.go:
    • Updated WriteClose to write ControlClose directly over the WebSocket under writeLock so close frames are sent even when gwCtx is canceled on timeout
    • Updated ReceiveWithContext to check channel closure and return context.Cause(w.ctx)
  • test/suites/basic.sh:
    • Added a test case in test_interactive verifying that both the initiator and joiner receive "Session timeout exceeded"

Checklist

Related issues

@roosterfish roosterfish left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for taking a look at this, please see my comments on the test.

Comment thread test/suites/basic.sh Outdated
Comment thread test/suites/basic.sh Outdated
Comment thread test/suites/basic.sh Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR improves MicroCloud’s WebSocket session handling so that when a trust establishment session expires, the client receives the real cancellation cause (e.g., “Session timeout exceeded”) rather than a generic abnormal WebSocket EOF error, aligning behavior with issue #1245.

Changes:

  • Adjusts the WebSocket gateway close-path to reliably send a ControlClose message even when the gateway context is canceled.
  • Improves receive behavior to surface the gateway context cause when the receive channel closes.
  • Adds a system test intended to validate that both initiator and joiner surface the timeout error.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
client/websocket.go Ensures close-cause forwarding works reliably and improves receive-side error reporting when the socket closes.
test/suites/basic.sh Adds an interactive test case intended to validate session-timeout error propagation to initiator/joiner.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/suites/basic.sh Outdated
Comment thread test/suites/basic.sh Outdated
Comment thread client/websocket.go Outdated
@shardool-patil

Copy link
Copy Markdown
Author

@roosterfish i have addressed all the requested changes:

  • test/suites/basic.sh: Set EXPECT_PEERS=2, clarified the test comment and echo message to verify both initiator and joiner, and added the missing EOF newline[cite: 2, 3].
  • test/includes/microcloud.sh: Updated microcloud_interactive to pass ${SESSION_TIMEOUT:-60} dynamically and added SESSION_TIMEOUT to unset_interactive_vars[cite: 2].

take a look whenever possible

@shardool-patil
shardool-patil force-pushed the fix/session-timeout-forwarding branch from b287951 to 4ffe082 Compare August 21, 2026 11:28
@roosterfish

Copy link
Copy Markdown
Contributor

See the test failure https://github.com/canonical/microcloud/actions/runs/32477357735/job/96766897819?pr=1519#step:3:45500.
I suspect what is happening is that the session expires too early now. Maybe let's leave the 60s default (no need to make it configurable in this case).

@roosterfish
roosterfish marked this pull request as draft August 21, 2026 15:41
@shardool-patil
shardool-patil force-pushed the fix/session-timeout-forwarding branch from 4ffe082 to 14a4c0b Compare August 21, 2026 16:41
@shardool-patil

Copy link
Copy Markdown
Author

See the test failure https://github.com/canonical/microcloud/actions/runs/32477357735/job/96766897819?pr=1519#step:3:45500. I suspect what is happening is that the session expires too early now. Maybe let's leave the 60s default (no need to make it configurable in this case).

@roosterfish I have worked and handled the requested changes so take a look at the code.

@roosterfish
roosterfish marked this pull request as ready for review August 24, 2026 11:42
@shardool-patil
shardool-patil force-pushed the fix/session-timeout-forwarding branch from 14a4c0b to c3f95c6 Compare August 25, 2026 02:27
@shardool-patil

Copy link
Copy Markdown
Author

@roosterfish I switched the test to reset_systems 3 0 0 because with reset_systems 2 0 0, micro03 is paused by default. Since EXPECT_PEERS=2 triggers table:expect 2, micro01 hangs during mDNS peer discovery waiting for a nonexistent second peer before the session ever starts. Running reset_systems 3 0 0 allows micro01 to discover both peers, select them, connect with micro02, and wait for micro03 until the session timeout triggers. Does reset_systems 3 0 0 work for you here?

@shardool-patil
shardool-patil force-pushed the fix/session-timeout-forwarding branch from c3f95c6 to e98fe8e Compare August 25, 2026 16:16
@shardool-patil

Copy link
Copy Markdown
Author

@roosterfish I've updated test/suites/basic.sh:

  • Switched to reset_systems 3 0 0 and disabled storage setup (SETUP_ZFS="no", SETUP_CEPH="no").
  • Kept EXPECT_PEERS=2 with join_session init micro01 micro02 so micro01 connects with micro02 and waits for micro03 until the default 60s session timeout triggers.
    With reset_systems 2 0 0, micro03 is paused by default, causing micro01 to hang during mDNS peer discovery (table:expect 2) before the session can start. 3 0 0 keeps micro03 running so discovery succeeds without configuring any unused storage devices.

Please take a look at this setup before re-running the tests.

Comment thread client/websocket.go Outdated
Comment thread client/websocket.go Outdated
Comment thread test/includes/microcloud.sh Outdated
Comment thread test/includes/microcloud.sh Outdated
@shardool-patil
shardool-patil force-pushed the fix/session-timeout-forwarding branch 2 times, most recently from e0eafdf to e5601b6 Compare August 26, 2026 15:48
@roosterfish
roosterfish requested a lite review from Copilot August 26, 2026 15:59
Comment thread test/suites/basic.sh

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

test/suites/basic.sh:447

  • The new test claims a short session timeout, but join_session runs microcloud init/add with a hard-coded --session-timeout=60 (see test/includes/microcloud.sh:200-204). As written, this test will likely wait ~60s for the session to expire, which slows the suite and doesn’t match the PR description (SESSION_TIMEOUT=5). Consider making the timeout configurable (e.g., env var) and setting it to 5 for this case.
  # Initiate a MicroCloud cluster with a session timeout and verify both initiator and joiner receive "Session timeout exceeded".
  reset_systems 3 0 0
  unset_interactive_vars

  echo "Initiate a MicroCloud cluster with a short session timeout and verify both initiator and joiner receive the timeout error"

Comment thread client/websocket.go
Comment on lines 143 to 147
func (w *WebsocketGateway) Write(v any) error {
w.writeLock.Lock()
defer w.writeLock.Unlock()

if w.ctx.Err() != nil {
return context.Cause(w.ctx)
}

return w.conn.WriteJSON(v)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@shardool-patil have you seen this one?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I've restored the cancellation guard in Write()

@shardool-patil
shardool-patil force-pushed the fix/session-timeout-forwarding branch from e5601b6 to b1cfb10 Compare August 26, 2026 17:03
Comment thread client/websocket.go
return fmt.Errorf("Failed to write control message: %w", writeErr)
}

// Wait on the other end to hang up.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why don't we have to wait anymore for the other site to hang up in case we signal close of the websocket?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

While debugging earlier test failures, I removed that trying to simplify WriteClose, but realized in hindsight that was a mistake. We definitely still need to wait for the other end to hang up so the connection isn't severed before the close frame is processed. I've restored the wait logic, so take a look at this when you get time @roosterfish

@shardool-patil
shardool-patil force-pushed the fix/session-timeout-forwarding branch from b1cfb10 to c6fb5c0 Compare September 3, 2026 02:30
@shardool-patil

Copy link
Copy Markdown
Author

@roosterfish just a ping, to check the status

Comment thread client/websocket.go
// Cancel the inner context with the respective error.
defer gwCancel(errors.New(controlClose.ControlMessage))
var closeErr error
if controlClose.ControlMessage != "" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you please add a comment why we are overriding the received control message here if it is set?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added an inline comment

Comment thread client/websocket.go
Comment on lines 143 to 147
func (w *WebsocketGateway) Write(v any) error {
w.writeLock.Lock()
defer w.writeLock.Unlock()

if w.ctx.Err() != nil {
return context.Cause(w.ctx)
}

return w.conn.WriteJSON(v)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@shardool-patil have you seen this one?

Comment thread client/websocket.go
// Unlike the actual websocket control close message this supports message longer than 125 bytes
// Unlike the actual websocket control close message this supports messages longer than 125 bytes
// as well as special characters.
// It waits for the other side to hang up or the gateway's context being cancelled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why dropping this comment?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Restored.

Comment thread client/websocket.go
err = json.Unmarshal(bytes, v)
case data, ok := <-w.Receive():
if !ok {
if w.ctx.Err() != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please add a comment when this is happening?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added an explanatory comment

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Preserve the write cancellation guard while bypassing it only for close frames, and ensure the test actually applies the five-second timeout.

3 open issues

Review effort: Lite

Comment thread client/websocket.go Outdated
Comment on lines 159 to 161
writeErr := w.Write(ControlClose{
ControlMessage: err.Error(),
ControlMessage: msg,
})

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in the latest push. Restored the cancellation guard in Write()

Comment thread test/suites/basic.sh
export SETUP_CEPH="no"
export OVN_WARNING="no"

! join_session init micro01 micro02 || false

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The test runs against the default 60-second timeout rather than requiring changes to shared testbed helpers in test/includes/microcloud.sh. I have updated the PR description to remove the reference to SESSION_TIMEOUT="5".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Signed-off-by: Shardool Patil <shardoolpatil999@gmail.com>
Signed-off-by: Shardool Patil <shardoolpatil999@gmail.com>
@shardool-patil
shardool-patil force-pushed the fix/session-timeout-forwarding branch from 23e0335 to b0b4e85 Compare October 26, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session expiry isn't forwarded to the client

3 participants