apache / apache/servicecomb-java-chassis

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

Open
#4,835 4 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
1.9k
Forks
814
Avg merge
8d 23h
Merged PRs (30d)
1

Description

### 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 差异的代码在于:

https://github.com/apache/servicecomb-java-chassis/blob/6873ee5992ff2211a1e0efa33dd30377a914ecbf/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadPart.java#L37-L40

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

https://github.com/apache/servicecomb-java-chassis/blob/f3dc998d78394fec8d6755192a57ab7ea889b859/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/FileUploadPart.java#L37-L40

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

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

回溯 Java-Chassis 的代码修改记录可知, 这个变化是为了修复另外一个问题而引入的:
https://github.com/apache/servicecomb-java-chassis/issues/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 的打开流的调用记录的监控, 便于辅助业务分析此类文件句柄泄漏问题的触发代码位置.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.