Skip to content

[BUG] - Java-Chassis 新版本无法自动清理临时上传文件的句柄 #4835

Open
@yhs0092

Description

@yhs0092

Steps to Reproduce

  1. 编写一个处理文件上传请求的controller.
  2. 接收到上传文件参数后, 打开文件流, 不关闭, 直接返回.
  3. 在Java-Chassis 1.x分支上可以观察到一段时间后文件句柄会被清理, 而在 Java-Chassis 2.8.24 版本, 文件句柄会一直存在, 直到进程重启才会消失.

注意:

  1. 实际测试发现此问题在Windows上不会出现(Java8), 我们是在Linux上复现的问题.
  2. 打开文件流不关闭只是为了让问题必现. 实际上即使业务代码有正常的close调用也可能遇到类似的问题, 因为业务处理文件流的逻辑可能超时, 导致 Vert.x 执行临时文件清理回调时文件流仍然处于打开的状态.

Expected Behavior

预期 Java-Chassis 2.8.24 能像 Java-Chassis 1.x 分支一样, 能够自动兜底清理文件句柄.

Servicecomb Version

2.8.24

Additional Context

根因分析

导致 Java-Chassis 1.x 和 2.8.x 差异的代码在于:

@Override
public InputStream getInputStream() throws IOException {
return Files.newInputStream(new File(fileUpload.uploadedFileName()).toPath());
}

在更早的版本中, 它返回的是一个 FileInputStream:

@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(fileUpload.uploadedFileName());
}

而 2.8.20 版本对此做了上述修改, 实测返回的是 sun.nio.ch.ChannelInputStream 类型的流.

在 Java8 中, FileInputStreamfinalize 方法会执行 close 方法, 而 sun.nio.ch.ChannelInputStreamfinalize 方法是空的. 这就导致 Java-Chassis 2.8.19 及之前的版本, 即使业务代码由于各种各样的原因没有执行 close 方法, FileInputStream 也能在被GC回收时兜底关闭文件句柄, 而在升级到 Java-Chassis 2.8.20 之后就只能泄漏句柄了.

回溯 Java-Chassis 的代码修改记录可知, 这个变化是为了修复另外一个问题而引入的:
#4476

解决思路

Java-Chassis 已经不适合将文件输入流的类型改回 FileInputStream 了.

除了上面说的 issue #4476 的原因, 高版本的 Java 还废弃了 finalize 方法, 即使用回 FileInputStream 也不能兜底关闭文件句柄.
为了能够同时兼容 Java8 和 Java21, 也不适合选用 Cleaner 机制(Java9才具备此特性).

目前能想到的解决办法:

  1. 基于 Java-Chassis 自身的 Invocation 生命周期管理机制来做自动清理动作.

    此方案的风险在于, 考虑到业务可能在 controller 层获取上传文件参数后, 异步调度到其他任务线程池处理, 因此在 Invocation 的结束事件发生时就直接关闭流的话可能会导致业务视角看到的Java-Chassis框架行为发生了影响业务功能的不兼容变化.

  2. 记录已打开的 InputStream, 在后台定时任务中监控其关闭状态.

    此方案可以一定程度规避方案1的风险, 但存在内存用量上升甚至泄漏的风险, 若选择这个思路, 也需要小心进行可靠性设计.

此外, 建议Java-Chassis增加对 FileUploadPart 的打开流的调用记录的监控, 便于辅助业务分析此类文件句柄泄漏问题的触发代码位置.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions