使用TestServer+Task.WhenAll并发请求时触发System.ObjectDisposedException(HttpContext已释放)的问题排查与解决求助
TestServer+Task.WhenAll并发请求时触发System.ObjectDisposedException(HttpContext已释放)的问题排查与解决求助
各位大佬好,我最近在做一个并发测试场景,想模拟多个用户同时通过自定义中间件发起请求。原本计划用TestServer,把每个请求包装成独立的Task,再通过Task.WhenAll实现并发,大部分情况都能正常跑,但碰到了一个棘手的问题——当中间件里调用context.AuthenticateAsync和context.SignInAsync时,会抛出如下异常:
fail: [namespace].[mycustommiddleware][0] Request has finished and HttpContext disposed. Object name: 'HttpContext'. System.ObjectDisposedException: Request has finished and HttpContext disposed. Object name: 'HttpContext'. at Microsoft.AspNetCore.Http.DefaultHttpContext.ThrowContextDisposed() at Microsoft.AspNetCore.Http.DefaultHttpContext.get_Features() at Microsoft.AspNetCore.Authentication.RequestPathBaseCookieBuilder.Build(HttpContext context, DateTimeOffset expiresFrom) at Microsoft.AspNetCore.Http.CookieBuilder.Build(HttpContext context) at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.BuildCookieOptions() at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
以下是我设置任务和创建请求的测试代码:
[Test] public async Task KeepMvcSessionAliveMiddleware_ShouldNotMixTokens() { // Arrange var baseUrl = $"http://localhost:{5050}"; var companies = new[] { "ACME", "Globex", "Initech", "Umbrella", "WayneTech" }; var usersPerCompany = 3; var userPool = companies .SelectMany(company => Enumerable.Range(1, usersPerCompany).Select(i => { var tokenDescriptor = GetTokenDescriptor(company); var refreshTokenDescriptor = GetRefreshTokenDescriptor(); return new { UserName = $"{company}_User{i}", Company = company, Token = _tokenHandler.CreateToken(tokenDescriptor), RefreshToken = _tokenHandler.CreateToken(refreshTokenDescriptor) }; })) .ToArray(); using (var warmupHandler = new HttpClientHandler { CookieContainer = new CookieContainer() }) using (var warmupClient = new HttpClient(warmupHandler) { BaseAddress = new Uri(baseUrl) }) { var warmupCookie = CreateCookie("WarmUp", "Test", _dataProtectionProvider); warmupHandler.CookieContainer = warmupCookie; await warmupClient.GetAsync("/warmup"); } // Act var tasks = Enumerable.Range(1, 50).Select(async i => { var random = new Random(); var user = userPool[random.Next(userPool.Length)]; using (var handler = new HttpClientHandler { CookieContainer = CreateCookie(user.Company, user.UserName, _dataProtectionProvider) }) using (var client = new HttpClient(handler) { BaseAddress = new Uri(baseUrl) }) { handler.CookieContainer.Add(new Cookie("RefreshToken", user.RefreshToken, "/", "localhost")); handler.CookieContainer.Add(new Cookie("Token", user.Token, "/", "localhost")); var task = client.GetAsync("/"); _activeRequests.Add(task); var response = await task; response.EnsureSuccessStatusCode(); Assert.That(response, Is.Not.Null); } }); await Task.WhenAll(tasks.ToArray()); foreach (var log in _loggerProvider.Logs) { TestContext.WriteLine(log); } }
我自己琢磨着,会不会是HttpContext被多个客户端共享了?后续请求用到的HttpContext已经被之前的请求释放掉了?但我实在搞不懂用TestServer做并发测试时,这里面的底层逻辑到底哪里理解错了,有没有办法避免HttpContext在请求之间被释放呢?
对了,补充一下中间件的InvokeAsync方法代码:
public async Task InvokeAsync(HttpContext context) { try { var jwtExp = _configuration.GetValue<int>("JWT:Expiration", 2); if (context.Response.HasStarted) return; // Check if the user is authenticated if (!RequestingStaticFile(context) && context.User.Identity!.IsAuthenticated) { var model = GetTokenCookies(context); if (string.IsNullOrEmpty(model.RefreshToken) || (await IsRefreshTokenExpired(model.RefreshToken))) { _logger.Log(LogLevel.Information, $"Refresh token expired or missing. Logging out user: {context.User.Identity.Name}"); // Refresh token has expired. End the session await context.SignOutAsync(); // Call the next middleware in the pipeline (which will be YARP) await _next(context); return; } // Check the cache for the session timeout value var companyName = context.User.Claims.FirstOrDefault(c => c.Type == "Company")?.Value.Replace(" ", string.Empty) ?? "Company"; var cacheKey = $"SessionTimeout_{companyName}_{context.User.Identity.Name}"; // Attempt to retrieve the cached session timeout...(原内容未完整部分) } // 后续中间件逻辑... } catch (Exception ex) { // 异常处理逻辑... } }
内容来源于stack exchange
相关产品推荐
相关产品推荐

