From 3acdc77f4009213951453df78220f5c291d03f3e Mon Sep 17 00:00:00 2001 From: Fedor Korotkov Date: Fri, 1 May 2026 13:47:41 -0400 Subject: [PATCH] Keep reconnectable exec attachments open after exit --- internal/controller/exec_sessions.go | 5 ++++- internal/controller/exec_sessions_test.go | 25 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/internal/controller/exec_sessions.go b/internal/controller/exec_sessions.go index cb186ac..7357cec 100644 --- a/internal/controller/exec_sessions.go +++ b/internal/controller/exec_sessions.go @@ -606,7 +606,10 @@ func (session *execSession) markFinished() { session.expiryTimer = time.AfterFunc(session.exitTTL, session.expire) } - subscribers := session.takeSubscribersLocked() + var subscribers []*execSessionSubscriber + if shouldClose { + subscribers = session.takeSubscribersLocked() + } session.mu.Unlock() closeSubscribers(subscribers) diff --git a/internal/controller/exec_sessions_test.go b/internal/controller/exec_sessions_test.go index 1d413d1..cd19eb6 100644 --- a/internal/controller/exec_sessions_test.go +++ b/internal/controller/exec_sessions_test.go @@ -293,3 +293,28 @@ func TestExecSessionFinishedEntryExpiresAfterTTL(t *testing.T) { return !ok }, time.Second, 10*time.Millisecond) } + +func TestExecSessionFinishKeepsReconnectableSubscriberOpen(t *testing.T) { + registry := newExecSessionRegistry() + session := newManualExecSessionForTest(execSessionKey{vmName: "vm", sessionID: "session"}, registry) + + subscriber, err := session.attach() + require.NoError(t, err) + + session.recordFrame(&execstream.Frame{Type: execstream.FrameTypeStdout, Data: []byte("out")}) + session.recordFrame(&execstream.Frame{ + Type: execstream.FrameTypeExit, + Exit: &execstream.Exit{Code: 0}, + }) + session.markFinished() + + require.Equal(t, execstream.FrameTypeStdout, (<-subscriber.frames).Type) + require.Equal(t, execstream.FrameTypeExit, (<-subscriber.frames).Type) + + session.sendHistory(subscriber, 0) + + noMoreHistory, ok := <-subscriber.frames + require.True(t, ok) + require.Equal(t, execstream.FrameTypeNoMoreHistory, noMoreHistory.Type) + require.EqualValues(t, 2, noMoreHistory.Watermark) +}