GlobalExceptionHandler本地集成测试失败:抛出OutOfMemoryException时出现Error while copying content to a stream错误
我来帮你梳理下这个问题的核心原因和解决思路:
问题根源
你遇到的情况是因为OutOfMemoryException属于CLR级别的致命异常,当这个异常被抛出时,CLR的内存资源已经处于极度紧张的状态,甚至可能无法分配足够的内存来完成后续的异常处理流程——比如你的GlobalExceptionHandler里尝试序列化ProblemDetails到响应流的操作,就会因为内存不足而失败,进而导致测试端读取响应时触发IOException和HttpRequestException。
而CI/CD环境看起来正常,大概率是因为CI的容器/虚拟机资源配置和本地不同,或者测试在CI中运行时并没有真的耗尽内存(比如JIT编译、内存分配的上下文差异),所以异常处理流程能侥幸完成。
另外,你提到即使把TryHandleAsync的返回值改成true也没用,这是因为问题出在异常处理的执行阶段,而不是后续的中间件是否继续执行。
可行的解决方案
针对这类致命异常的测试和处理,你可以从以下几个方向调整:
在测试中模拟异常,而非真的抛出OutOfMemoryException
直接抛出OutOfMemoryException会让CLR陷入不稳定状态,不如在测试中使用依赖注入替换掉异常抛出的逻辑,或者用Mock框架模拟一个OutOfMemoryException传入异常处理器,单独测试处理器的逻辑,而不是通过真实的HTTP请求触发:// 示例:单独测试GlobalExceptionHandler的单元测试 [Fact] public async Task OutOfMemoryException_ShouldGenerateCorrectProblemDetails() { // Arrange var loggerMock = new Mock<ILoggerWrapper<GlobalExceptionHandler>>(); var handler = new GlobalExceptionHandler(loggerMock.Object); var httpContext = new DefaultHttpContext(); var exception = new OutOfMemoryException(); // Act var result = await handler.TryHandleAsync(httpContext, exception, CancellationToken.None); var problemDetails = await httpContext.Response.ReadFromJsonAsync<ProblemDetails>(); // Assert problemDetails.Status.Should().Be(500); problemDetails.Title.Should().Be("There was an error, Out of memory. Please see logs for more information."); loggerMock.Verify(l => l.LogCritical(exception), Times.Once); }调整异常处理器对致命异常的处理逻辑
对于OutOfMemoryException、StackOverflowException这类致命异常,不要尝试序列化复杂的ProblemDetails,而是返回一个极简的文本响应,减少内存分配:case OutOfMemoryException: var errorMsg = "There was an error, Out of memory. Please see logs for more information."; // 直接返回文本,避免JSON序列化的内存开销 httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; httpContext.Response.ContentType = "text/plain"; await httpContext.Response.WriteAsync(errorMsg, cancellationToken); logAction(exception); return false;在集成测试中捕获致命异常
如果你坚持要通过HTTP请求测试,可以在测试代码中捕获HttpRequestException,并验证异常链中是否包含OutOfMemoryException,以此来确认异常处理器是否被触发:[Fact] public async Task OutOfMemoryException_ShouldTriggerCriticalLogAndReturn500() { // Arrange var client = _factory.CreateClient(); // Act var exception = await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync("throw-out-of-memory")); // Assert // 验证异常根源是OutOfMemoryException Assert.IsType<OutOfMemoryException>(exception.InnerException?.InnerException); // 这里可以结合日志验证,比如验证LogCritical是否被调用 }
额外提示
StackOverflowException也是类似的情况——CLR在抛出这个异常时,调用栈已经溢出,几乎无法执行任何后续代码,所以你的异常处理器大概率也无法正常处理它,同样需要用模拟测试的方式来验证逻辑。
备注:内容来源于stack exchange,提问作者3xGuy

