perf(server): skip IdleTrackingBackgroundService timer in stateless mode (#1531)
This commit is contained in:
parent
ed19286a08
commit
a4157f310f
2 changed files with 61 additions and 0 deletions
|
|
@ -31,6 +31,17 @@ internal sealed partial class IdleTrackingBackgroundService : BackgroundService
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// In stateless mode there are no sessions to track, so skip starting the periodic timer entirely.
|
||||||
|
if (_options.Value.Stateless)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.StartAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using ModelContextProtocol.AspNetCore.Tests.Utils;
|
using ModelContextProtocol.AspNetCore.Tests.Utils;
|
||||||
using ModelContextProtocol.Protocol;
|
using ModelContextProtocol.Protocol;
|
||||||
|
|
@ -184,6 +185,55 @@ public class HttpMcpServerBuilderExtensionsTests(ITestOutputHelper testOutputHel
|
||||||
Assert.Null(options.SessionMigrationHandler);
|
Assert.Null(options.SessionMigrationHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IdleTrackingBackgroundService_DoesNotStartTimer_WhenStateless()
|
||||||
|
{
|
||||||
|
Builder.Services
|
||||||
|
.AddMcpServer()
|
||||||
|
.WithHttpTransport(options => options.Stateless = true);
|
||||||
|
|
||||||
|
using var app = Builder.Build();
|
||||||
|
|
||||||
|
var idleTrackingService = GetIdleTrackingService(app.Services);
|
||||||
|
Assert.NotNull(idleTrackingService);
|
||||||
|
|
||||||
|
await idleTrackingService.StartAsync(TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
// BackgroundService.ExecuteTask is only set when ExecuteAsync has been kicked off via base.StartAsync.
|
||||||
|
// In stateless mode we early-return, so ExecuteTask should remain null.
|
||||||
|
Assert.Null(idleTrackingService.ExecuteTask);
|
||||||
|
|
||||||
|
await idleTrackingService.StopAsync(TestContext.Current.CancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IdleTrackingBackgroundService_StartsTimer_WhenStateful()
|
||||||
|
{
|
||||||
|
Builder.Services
|
||||||
|
.AddMcpServer()
|
||||||
|
.WithHttpTransport();
|
||||||
|
|
||||||
|
using var app = Builder.Build();
|
||||||
|
|
||||||
|
var idleTrackingService = GetIdleTrackingService(app.Services);
|
||||||
|
Assert.NotNull(idleTrackingService);
|
||||||
|
|
||||||
|
await idleTrackingService.StartAsync(TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
// In the default (stateful) mode the timer loop must start, so ExecuteTask should be set.
|
||||||
|
Assert.NotNull(idleTrackingService.ExecuteTask);
|
||||||
|
|
||||||
|
await idleTrackingService.StopAsync(TestContext.Current.CancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BackgroundService? GetIdleTrackingService(IServiceProvider services)
|
||||||
|
{
|
||||||
|
// IdleTrackingBackgroundService is internal, so look it up by type name from the registered IHostedService instances.
|
||||||
|
return services.GetServices<IHostedService>()
|
||||||
|
.OfType<BackgroundService>()
|
||||||
|
.FirstOrDefault(s => s.GetType().Name == "IdleTrackingBackgroundService");
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class StubSessionMigrationHandler : ISessionMigrationHandler
|
private sealed class StubSessionMigrationHandler : ISessionMigrationHandler
|
||||||
{
|
{
|
||||||
public ValueTask<InitializeRequestParams?> AllowSessionMigrationAsync(
|
public ValueTask<InitializeRequestParams?> AllowSessionMigrationAsync(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue