1、描述异常背景:
因为需要分析数据,待处理excel文件的数据行数太大,手动太累,花半小时写了一个定制的数据入库工具,改成了通用的,整个项目中的万级别数据都在工具上分析,写SQL进行分析,但是遇到很疑惑的问题,文件上传结束收流时,tomcat的DisFileItem类的delete方法会自动调用,但是如果当前系统用户的权限不足,或是文件流在删除该临时文件之前未关闭都会导致删除失败,虽然接收流的业务操作都结束了,但是很是会报出糟心的UncheckedIOException:Cannot delete tomcat默认的临时文件路径+_00000000.tmp异常(剧透一下:运行时未关闭输入流,JVM还在等待用户关闭,存在引用无法回收,所以手动跟着路径去删除你都删不掉,所以跟着源码找根源)。
2、查找异常原因(并不是所有的异常都是你显式的写出来的,特别是轮子使用不仔细的内部异常)
以上请及其注意异常出现的根源:StandardServletMultipartResolver.java:134;
我们进去查看其源码得到
也就是说我们的请求文件接收成功了吗,但是删除临时文件的操作出现问题了:
if (request.getFile(part.getName()) != null) {
part.delete();
}
进入删除的代码下来:
注意这一句源码注释:
删除部件的基础存储,包括删除任何关联的临时磁盘文件。尽管容器将自动删除此存储,但此方法可用于确保在较早的时间执行此操作,从而保留系统资源。
仅当对部件实例进行垃圾回收时,才需要容器删除关联的存储。Apache Tomcat 将在关联的请求完成处理后删除关联的存储。其他容器的行为可能不同。
抛出:
IOException – 如果在尝试删除部件时发生 I/O
那么我们进入到它的默认实现类的最终源码:
到这里我们大致是知道问题出在这个临时文件的删除上了。
但是为什么删除失败呢?只有可能是这行代码的问题:
如果输出的文件就是有问题的null那一定是删除不掉的,让我们找找源码:
这里一定是没有问题的,继续往下走:
由此处的信息可以知道作为缓冲的临时文件区域的输出流未完成,还在保持开启状态,很大的可能是缓冲前的输入的流还没完全关闭(我们明确知道的是在代码里获取了输入的流但是没有对流进行关闭),亦或有可能原本文件上传就是null(当然不可能了兄弟)
3、结合2查找真正导致输出文件流在删除之前关闭的原因
我们注意到简单的接口代码是:
@PostMapping("/upTopGoods")
@ResponseBody
public void upTopGoods(@RequestParam("fileName") MultipartFile fileName){
if (Objects.nonNull(fileName)){
excelUtil.setTopGoods(fileName);
}
}
这个流会在操作完成或输入流的字节被处理完之后没能主动关闭的话,很有可能出自于接口MultipartFile的性质需要我们自己关闭(所以才会有一次OOM异常,但是请注意一定不仅仅是这个导致的,BUG选手程序员要保持疑问)。
所以我们查看MultipartFile的源码试图找出问题所在:
以上是MultipartFile的输入流方法注释。
以上处理完数据之后我们对输入的流进行了关闭,测试一下看看是否解决了问题:
通过结果来看是完全没有问题的了。
本次排查问题寻根原理过程结束,拜拜!