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
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@

namespace ModelContextProtocol.AspNetCore;

internal sealed partial class IdleTrackingBackgroundService(
StatefulSessionManager sessions,
IOptions<HttpServerTransportOptions> options,
IHostApplicationLifetime appLifetime,
ILogger<IdleTrackingBackgroundService> logger) : BackgroundService
internal sealed partial class IdleTrackingBackgroundService : BackgroundService
{
// Workaround for https://github.com/dotnet/runtime/issues/91121. This is fixed in .NET 9 and later.
private readonly ILogger _logger = logger;
private readonly StatefulSessionManager _sessions;
private readonly IOptions<HttpServerTransportOptions> _options;
private readonly IHostApplicationLifetime _appLifetime;
private readonly ILogger _logger;

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
public IdleTrackingBackgroundService(
StatefulSessionManager sessions,
IOptions<HttpServerTransportOptions> options,
IHostApplicationLifetime appLifetime,
ILogger<IdleTrackingBackgroundService> logger)
{
// Still run loop given infinite IdleTimeout to enforce the MaxIdleSessionCount and assist graceful shutdown.
if (options.Value.IdleTimeout != Timeout.InfiniteTimeSpan)
Expand All @@ -23,14 +25,22 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)

ArgumentOutOfRangeException.ThrowIfLessThan(options.Value.MaxIdleSessionCount, 0);

_sessions = sessions;
_options = options;
_appLifetime = appLifetime;
_logger = logger;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
var timeProvider = options.Value.TimeProvider;
var timeProvider = _options.Value.TimeProvider;
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5), timeProvider);

while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
{
await sessions.PruneIdleSessionsAsync(stoppingToken);
await _sessions.PruneIdleSessionsAsync(stoppingToken);
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
Expand All @@ -40,15 +50,15 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
await sessions.DisposeAllSessionsAsync();
await _sessions.DisposeAllSessionsAsync();
}
finally
{
if (!stoppingToken.IsCancellationRequested)
{
// Something went terribly wrong. A very unexpected exception must be bubbling up, but let's ensure we also stop the application,
// so that it hopefully gets looked at and restarted. This shouldn't really be reachable.
appLifetime.StopApplication();
_appLifetime.StopApplication();
IdleTrackingBackgroundServiceStoppedUnexpectedly();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,7 @@ public async ValueTask DisposeAsync()
base.Dispose();
}

#if !NET10_0
[Fact]
#else
[Fact(Skip = "https://github.com/modelcontextprotocol/csharp-sdk/issues/823")]
#endif
public async Task NegativeNonInfiniteIdleTimeout_Throws_ArgumentOutOfRangeException()
{
Builder.Services.AddMcpServer().WithHttpTransport(options =>
Expand All @@ -73,11 +69,7 @@ public async Task NegativeNonInfiniteIdleTimeout_Throws_ArgumentOutOfRangeExcept
Assert.Contains("IdleTimeout", ex.Message);
}

#if !NET10_0
[Fact]
#else
[Fact(Skip = "https://github.com/modelcontextprotocol/csharp-sdk/issues/823")]
#endif
public async Task NegativeMaxIdleSessionCount_Throws_ArgumentOutOfRangeException()
{
Builder.Services.AddMcpServer().WithHttpTransport(options =>
Expand Down