Skip session activity tracking when sessionSweepInterval is null

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Daniel Garnier-Moiroux 2026-09-16 13:39:38 +02:00 • committed by Daniel Garnier-Moiroux
commit 1178539050
2 changed files with 33 additions and 22 deletions

View file

@ -125,7 +125,8 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
/** /**
* IDs of the sessions which received a request since the last sweep. The set is * IDs of the sessions which received a request since the last sweep. The set is
* swapped for an empty one on every sweep, so it only ever holds the activity of the * swapped for an empty one on every sweep, so it only ever holds the activity of the
* current interval. * current interval. Only populated when a sweeper runs, as nothing would ever swap it
* otherwise.
*/ */
private final AtomicReference<Set<String>> activeSessions = new AtomicReference<>(ConcurrentHashMap.newKeySet()); private final AtomicReference<Set<String>> activeSessions = new AtomicReference<>(ConcurrentHashMap.newKeySet());
@ -143,10 +144,10 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
private KeepAliveScheduler keepAliveScheduler; private KeepAliveScheduler keepAliveScheduler;
/** /**
* Periodic eviction of the sessions no client came back to. Activated if * Periodic eviction of the sessions no client came back to. {@code null} if no
* sessionSweepInterval is set. * sessionSweepInterval is set, in which case sessions are never reclaimed.
*/ */
private Disposable sessionSweeper; private final Disposable sessionSweeper;
/** /**
* Security validator for validating HTTP requests. * Security validator for validating HTTP requests.
@ -205,23 +206,27 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
this.keepAliveScheduler.start(); this.keepAliveScheduler.start();
} }
if (sessionSweepInterval != null) { this.sessionSweeper = sessionSweepInterval == null ? null : startSessionSweeper(sessionSweepInterval);
this.sessionSweeper = Flux.interval(sessionSweepInterval, sessionSweepInterval, Schedulers.boundedElastic()) }
.doOnNext(tick -> {
// Each sweep runs in its own subscription, so that a sweep failing
// does not terminate the interval and disable sweeping altogether
Mono.fromRunnable(this::sweepSessions)
.doOnError(e -> logger.error("Session sweep failed", e))
.onErrorComplete()
.subscribe();
})
.onErrorComplete(error -> {
logger.error("Session sweeper error", error);
return true;
})
.subscribe();
}
/**
* Schedules the periodic eviction of idle sessions.
* @param sessionSweepInterval the interval between two sweeps
* @return the handle on the scheduled sweeps, to be disposed when the transport shuts
* down
*/
private Disposable startSessionSweeper(Duration sessionSweepInterval) {
return Flux.interval(sessionSweepInterval, sessionSweepInterval, Schedulers.boundedElastic()).doOnNext(tick -> {
// Each sweep runs in its own subscription, so that a sweep failing does
// not terminate the interval and disable sweeping altogether
Mono.fromRunnable(this::sweepSessions)
.doOnError(e -> logger.error("Session sweep failed", e))
.onErrorComplete()
.subscribe();
}).onErrorComplete(error -> {
logger.error("Session sweeper error", error);
return true;
}).subscribe();
} }
/** /**
@ -257,6 +262,11 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
* @param sessionId the session the current request belongs to * @param sessionId the session the current request belongs to
*/ */
private void markSessionActive(String sessionId) { private void markSessionActive(String sessionId) {
if (this.sessionSweeper == null) {
// No sweep ever swaps the set of active sessions, so recording activity would
// only accumulate the ID of every session which ever issued a request
return;
}
this.activeSessions.get().add(sessionId); this.activeSessions.get().add(sessionId);
} }

View file

@ -448,13 +448,14 @@ class HttpServletStreamableIntegrationTests extends AbstractMcpClientServerInteg
@Test @Test
void sessionIsNotEvictedWithoutSweepInterval() throws Exception { void sessionIsNotEvictedWithoutSweepInterval() throws Exception {
// Sweeping is opt-in: a transport configured without an interval keeps its // Sweeping can be turned off: such a transport keeps its sessions until they are
// sessions until they are deleted or the server shuts down // deleted or the server shuts down
mcpServerTransportProvider.closeGracefully().block(); mcpServerTransportProvider.closeGracefully().block();
mcpServerTransportProvider = HttpServletStreamableServerTransportProvider.builder() mcpServerTransportProvider = HttpServletStreamableServerTransportProvider.builder()
.contextExtractor(TEST_CONTEXT_EXTRACTOR) .contextExtractor(TEST_CONTEXT_EXTRACTOR)
.mcpEndpoint(MESSAGE_ENDPOINT) .mcpEndpoint(MESSAGE_ENDPOINT)
.keepAliveInterval(KEEP_ALIVE_INTERVAL) .keepAliveInterval(KEEP_ALIVE_INTERVAL)
.sessionSweepInterval(null)
.build(); .build();
MCP_SERVLET.setDelegate(mcpServerTransportProvider); MCP_SERVLET.setDelegate(mcpServerTransportProvider);
prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build(); prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0").build();