Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion server/streamable_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -1400,12 +1400,17 @@ func (s *StreamableHTTPServer) nextRequestID(sessionID string) int64 {

// touchSession records the current time as the last activity for the given session.
// It is a no-op when the sweeper is disabled (sessionIdleTTL <= 0) or sessionID is empty.
// It runs on every request, so a known session is found with Load and touched
// without allocating; only a session's first touch pays for LoadOrStore.
func (s *StreamableHTTPServer) touchSession(sessionID string) {
if sessionID == "" || s.sessionIdleTTL <= 0 {
return
}
now := time.Now().UnixNano()
actual, _ := s.sessionLastActive.LoadOrStore(sessionID, new(atomic.Int64))
actual, ok := s.sessionLastActive.Load(sessionID)
if !ok {
actual, _ = s.sessionLastActive.LoadOrStore(sessionID, new(atomic.Int64))
}
actual.(*atomic.Int64).Store(now)
}

Expand Down
41 changes: 41 additions & 0 deletions server/streamable_http_touch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package server

import (
"testing"
"time"

"github.com/stretchr/testify/require"
)

// newTouchTestServer builds a streamable server with the idle sweeper on, so
// touchSession is live; the caller shuts it down.
func newTouchTestServer() *StreamableHTTPServer {
return NewStreamableHTTPServer(NewMCPServer("test", "1.0.0"), WithSessionIdleTTL(time.Hour))
}

// TestTouchSessionKnownSessionAllocatesNothing pins that a session that is
// already tracked is touched without allocating: touchSession runs on every
// request once WithSessionIdleTTL is set.
func TestTouchSessionKnownSessionAllocatesNothing(t *testing.T) {
s := newTouchTestServer()
defer func() { _ = s.Shutdown(t.Context()) }()
const sessionID = "mcp-session-known"
s.touchSession(sessionID)

allocs := testing.AllocsPerRun(1000, func() { s.touchSession(sessionID) })
require.Zero(t, allocs, "touchSession on a known session must not allocate")
}

// BenchmarkTouchSessionKnown measures touchSession on a session that is
// already tracked, the per-request path once WithSessionIdleTTL is set.
func BenchmarkTouchSessionKnown(b *testing.B) {
s := newTouchTestServer()
defer func() { _ = s.Shutdown(b.Context()) }()
const sessionID = "mcp-session-known"
s.touchSession(sessionID)

b.ReportAllocs()
for b.Loop() {
s.touchSession(sessionID)
}
}
Loading