You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GlobalExceptionHandler本地集成测试失败:抛出OutOfMemoryException时出现Error while copying content to a stream错误

GlobalExceptionHandler本地集成测试失败:抛出OutOfMemoryException时出现Error while copying content to a stream错误

我来帮你梳理下这个问题的核心原因和解决思路:

问题根源

你遇到的情况是因为OutOfMemoryException属于CLR级别的致命异常,当这个异常被抛出时,CLR的内存资源已经处于极度紧张的状态,甚至可能无法分配足够的内存来完成后续的异常处理流程——比如你的GlobalExceptionHandler里尝试序列化ProblemDetails到响应流的操作,就会因为内存不足而失败,进而导致测试端读取响应时触发IOException和HttpRequestException。

而CI/CD环境看起来正常,大概率是因为CI的容器/虚拟机资源配置和本地不同,或者测试在CI中运行时并没有真的耗尽内存(比如JIT编译、内存分配的上下文差异),所以异常处理流程能侥幸完成。

另外,你提到即使把TryHandleAsync的返回值改成true也没用,这是因为问题出在异常处理的执行阶段,而不是后续的中间件是否继续执行。

可行的解决方案

针对这类致命异常的测试和处理,你可以从以下几个方向调整:

  1. 在测试中模拟异常,而非真的抛出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);
    }
    
  2. 调整异常处理器对致命异常的处理逻辑
    对于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;
    
  3. 在集成测试中捕获致命异常
    如果你坚持要通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 09:23:02