Trying to troubleshoot an occasional java.nio.file.DirectoryNotEmptyException
in a recursive delete method taken from Delete directories recursively in Java
尝试对偶尔出现的java.nio.file进行故障排除。在Java中递归地从删除目录中获取的递归删除方法中的DirectoryNotEmptyException
Code (credit to @TrevorRobinson) :
代码(学分@TrevorRobinson):
static void removeRecursive(Path path) throws IOException {
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException {
logger.warn("Deleting " + file.getFileName());
Files.delete(file);
logger.warn("DELETED " + file.getFileName());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
// try to delete the file anyway, even if its attributes could
// not be read, since delete-only access is theoretically possible
// I NEVER SEE THIS
logger.warn("Delete file " + file + " failed", exc);
try {
Files.delete(file);
} catch (IOException e) {
logger.warn(
"Delete file " + file + " failed again", exc);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
if (exc == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
// directory iteration failed; propagate exception
throw exc;
}
});
}
Call:
电话:
try {
removeRecursive(Paths.get(unzipDirPath));
} catch (IOException e) {
String msg = "Failed to delete folder " + unzipDirPath;
if (e instanceof java.nio.file.DirectoryNotEmptyException) {
msg += ". Still contains : ";
final File[] listFiles = Paths.get(unzipDirPath).toFile().listFiles();
if (listFiles != null) for (File file : listFiles) {
msg += file.getAbsolutePath() + "\n";
}
}
log.error(msg, e);
}
Prints (once in 20/40 iterations):
打印(20/40次迭代一次):
22:03:34.190 [http-bio-8080-exec-47] WARN g.u.d.m.server.servlets.Controller$1 - Deleting batt
22:03:34.192 [http-bio-8080-exec-47] WARN g.u.d.m.server.servlets.Controller$1 - DELETED batt
22:03:34.192 [http-bio-8080-exec-47] WARN g.u.d.m.server.servlets.Controller$1 - Deleting wifi
22:03:34.193 [http-bio-8080-exec-47] WARN g.u.d.m.server.servlets.Controller$1 - DELETED wifi
22:03:34.196 [http-bio-8080-exec-47] ERROR g.u.d.m.s.s.DataCollectionServlet - Failed to delete folder C:\yada\. Still contains : C:\yada\dir\wifi
java.nio.file.DirectoryNotEmptyException: C:\yada\dir
at sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:265) ~[na:1.7.0_45]
at sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:103) ~[na:1.7.0_45]
at java.nio.file.Files.delete(Files.java:1077) ~[na:1.7.0_45]
at gr.uoa.di.monitoring.server.servlets.Controller$1.postVisitDirectory(Controller.java:128) ~[Controller$1.class:na]
at gr.uoa.di.monitoring.server.servlets.Controller$1.postVisitDirectory(Controller.java:1) ~[Controller$1.class:na]
at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:224) ~[na:1.7.0_45]
at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:199) ~[na:1.7.0_45]
at java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:69) ~[na:1.7.0_45]
at java.nio.file.Files.walkFileTree(Files.java:2600) ~[na:1.7.0_45]
at java.nio.file.Files.walkFileTree(Files.java:2633) ~[na:1.7.0_45]
at gr.uoa.di.monitoring.server.servlets.Controller.removeRecursive(Controller.java:96) ~[Controller.class:na]
at gr.uoa.di.monitoring.server.servlets.DataCollectionServlet.doPost(DataCollectionServlet.java:153) ~[DataCollectionServlet.class:na]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:641) [servlet-api.jar:na]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) [servlet-api.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.32]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.32]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.32]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) [catalina.jar:7.0.32]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168) [catalina.jar:7.0.32]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [catalina.jar:7.0.32]
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929) [catalina.jar:7.0.32]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.32]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) [catalina.jar:7.0.32]
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1002) [tomcat-coyote.jar:7.0.32]
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585) [tomcat-coyote.jar:7.0.32]
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310) [tomcat-coyote.jar:7.0.32]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_45]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_45]
at java.lang.Thread.run(Thread.java:744) [na:1.7.0_45]
Notice that wifi
is reported as deleted - what's even more weird is that sometimes I get :
注意,wifi被报告为已删除——更奇怪的是,有时我收到:
Failed to delete folder C:\yada. Still contains : C:\yada\dir
无法删除文件夹C:\yada。仍然包含:C:\雅达\ dir
java.nio.file.DirectoryNotEmptyException: C:\yada\dir
java.nio.file。DirectoryNotEmptyException:C:\ yada \ dir
I tend towards the conclusion that occasionally the deletion takes too long - in other words the problem is that java.nio.file.Files.delete(Path path)
does not block (so C:\yada\dir still contains files when its time comes, which sometimes are deleted by the time I stat it). So how am I to workaround this ?
我倾向于这样的结论:偶尔删除会花费很长时间——换句话说,问题是java.nio.file. files . files .delete(Path)不会阻塞(所以C:\yada\dir在时间到来时仍然包含文件,有时会在我统计它时被删除)。那么我该如何解决这个问题呢?
Also : is java.nio.file.Files.delete(Path path)
required to throw ? The docs state :
同样:是否需要抛出java.nio.file.Files.delete(路径)?文档状态:
On some operating systems it may not be possible to remove a file when it is open and in use by this Java virtual machine or other programs.
在某些操作系统中,当文件打开并被此Java虚拟机或其他程序使用时,可能无法删除它。
Does not seem to require an exception to be thrown in this case. Is java.nio.file.Files.delete(Path path)
required to throw ?
在这种情况下似乎不需要抛出异常。是否需要抛出java.nio.file.Files.delete(路径)?
4 个解决方案
#1
7
I was having the same problem and it turned out the problem was caused by an unclosed directory file stream somewhere else in the code for the same directory I was deleting stuff from. The stream object returned by:
我遇到了同样的问题,结果发现这个问题是由一个未关闭的目录文件流引起的,在代码中的其他地方,我正在从同一个目录中删除内容。流对象返回:
Files.list(Path)
must be closed, so be sure to use the try-with-resources construct in your code if using that method.
必须关闭,所以如果使用该方法,请确保在代码中使用try- resources构造。
So I don't think it's that the deletion takes too long, I tried a wait before re-attempting directory deletion without any luck. It's most likely that your own program has that resource locked. The result is that the delete call on it doesn't complete although it returns successfully (it looks like Windows will eventually delete the file once your own program frees it) but then of course the containing directory cannot be deleted as that is indeed not yet empty.
所以我不认为删除需要太长时间,我尝试了等待,然后重新尝试删除目录。很可能您自己的程序已经锁定了该资源。结果是,对它的delete调用没有完成,尽管它成功返回(看起来Windows在您自己的程序释放该文件之后最终会删除该文件),但是当然,包含的目录不能被删除,因为该目录实际上还不是空的。
#2
1
I know this is a very old thread - but I had the same problem and it took me quite some time to get it fixed. I think this misbehaviour is caused by a timing issue (looks like it happens on Windows only), so I put a pause into the postVisitDirectory method. That worked and this is what I finally came up with:
我知道这是一条很旧的线——但我也有同样的问题,我花了很长时间才把它修好。我认为这种错误行为是由时间问题引起的(看起来只在Windows上发生),所以我在postVisitDirectory方法中添加了一个暂停。这招奏效了,这就是我最终想到的:
A method that does the delete without throwing the DirectoryNotEmptyException:
不抛出DirectoryNotEmptyException而进行删除的方法:
private boolean isDeleted(Path dir) throws IOException {
boolean deleted = false;
try {
Files.delete(dir);
deleted = true;
} catch (DirectoryNotEmptyException e) {
// happens sometimes if Windows is too slow to remove children of a directory
deleted = false;
}
return deleted;
}
and its use in a loop:
以及它在循环中的使用:
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
if (e == null) {
int maxTries = 5;
int count = 0;
boolean deleted = false;
do {
if ((deleted = this.isDeleted(dir))) {
break;
} else {
// wait a bit and try again
count++;
try {
Thread.sleep(1);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
break;
}
}
} while (count < maxTries);
// gone?
if (!deleted) {
throw new DirectoryNotEmptyException(dir.toString());
}
// go ahead
return FileVisitResult.CONTINUE;
}
throw e;
}
Andy
安迪
#3
0
You could temporarily store the name of the file you're currently iterating in a variable and perform a try-catch on DirectoryNotEmptyException
. When this exception occurs, catch it and throw your own exception that specifies the file.
您可以临时存储当前正在变量中迭代的文件的名称,并在DirectoryNotEmptyException上执行try-catch。当这个异常发生时,捕获它并抛出指定文件的您自己的异常。
try {
Files.delete(file);
} catch (DirectoryNotEmptyException e) {
throw new MySpecificException(file.getFileName());
}
class MySpecificException extends Exception {
public MySpecificException() { }
public MySpecificException(string filename) {
super(filename);
}
}
Getting the filename can be done with e.getMessage();
获取文件名可以使用e.getMessage();
I assume Files.delete()
continues deleting the files in a directory when it encounters a file that can't be deleted. If this is the case, my method still works: just return a list of all the files in the directory instead of the filename of the directory. It should only contain files that can't be deleted and you still have your solution.
假设文件。delete()在遇到无法删除的文件时继续删除目录中的文件。如果是这种情况,我的方法仍然有效:只返回目录中所有文件的列表,而不是目录的文件名。它应该只包含不能删除的文件,并且您仍然拥有您的解决方案。
#4
-1
An expansion on my comment above.
我上面评论的扩展。
When Java has a problem doing something it throws an Exception. Exceptions, like all other types, can be inherited from. The API will specify which checked exceptions each method throws. The methods in java.io
and java.nio
will usually throw IOException
or one of it's children. If you want to create a method that will tell you why the file operation, in this case deletion, failed you can do something like this:
当Java做某事有问题时,它会抛出异常。与所有其他类型一样,异常也可以继承。API将指定每个方法抛出的检查异常。在java的方法。io和java。nio通常会抛出IOException或者它的一个子元素。如果您想要创建一个方法来告诉您为什么文件操作(在本例中是删除操作)失败,您可以这样做:
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
//You don't need to try to delete the file again since this method was called
//because the deletion failed. Instead...
if (exc instanceof NoSuchFileException) {
System.out.println("Could not find the file: " + file);
} else if (exc instanceof DirectoryNotEmptyException) {
System.out.println("The directory [+ " file + "] was not empty.");
} else {
System.out.println("Could not delete file [" + file
+ "]. There was a problem communicating with the file system");
}
return FileVisitResult.CONTINUE;
}
You can change the actual program response as you need to but that should give you the general idea.
您可以根据需要更改实际的程序响应,但这应该会给您一个大致的概念。
#1
7
I was having the same problem and it turned out the problem was caused by an unclosed directory file stream somewhere else in the code for the same directory I was deleting stuff from. The stream object returned by:
我遇到了同样的问题,结果发现这个问题是由一个未关闭的目录文件流引起的,在代码中的其他地方,我正在从同一个目录中删除内容。流对象返回:
Files.list(Path)
must be closed, so be sure to use the try-with-resources construct in your code if using that method.
必须关闭,所以如果使用该方法,请确保在代码中使用try- resources构造。
So I don't think it's that the deletion takes too long, I tried a wait before re-attempting directory deletion without any luck. It's most likely that your own program has that resource locked. The result is that the delete call on it doesn't complete although it returns successfully (it looks like Windows will eventually delete the file once your own program frees it) but then of course the containing directory cannot be deleted as that is indeed not yet empty.
所以我不认为删除需要太长时间,我尝试了等待,然后重新尝试删除目录。很可能您自己的程序已经锁定了该资源。结果是,对它的delete调用没有完成,尽管它成功返回(看起来Windows在您自己的程序释放该文件之后最终会删除该文件),但是当然,包含的目录不能被删除,因为该目录实际上还不是空的。
#2
1
I know this is a very old thread - but I had the same problem and it took me quite some time to get it fixed. I think this misbehaviour is caused by a timing issue (looks like it happens on Windows only), so I put a pause into the postVisitDirectory method. That worked and this is what I finally came up with:
我知道这是一条很旧的线——但我也有同样的问题,我花了很长时间才把它修好。我认为这种错误行为是由时间问题引起的(看起来只在Windows上发生),所以我在postVisitDirectory方法中添加了一个暂停。这招奏效了,这就是我最终想到的:
A method that does the delete without throwing the DirectoryNotEmptyException:
不抛出DirectoryNotEmptyException而进行删除的方法:
private boolean isDeleted(Path dir) throws IOException {
boolean deleted = false;
try {
Files.delete(dir);
deleted = true;
} catch (DirectoryNotEmptyException e) {
// happens sometimes if Windows is too slow to remove children of a directory
deleted = false;
}
return deleted;
}
and its use in a loop:
以及它在循环中的使用:
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
if (e == null) {
int maxTries = 5;
int count = 0;
boolean deleted = false;
do {
if ((deleted = this.isDeleted(dir))) {
break;
} else {
// wait a bit and try again
count++;
try {
Thread.sleep(1);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
break;
}
}
} while (count < maxTries);
// gone?
if (!deleted) {
throw new DirectoryNotEmptyException(dir.toString());
}
// go ahead
return FileVisitResult.CONTINUE;
}
throw e;
}
Andy
安迪
#3
0
You could temporarily store the name of the file you're currently iterating in a variable and perform a try-catch on DirectoryNotEmptyException
. When this exception occurs, catch it and throw your own exception that specifies the file.
您可以临时存储当前正在变量中迭代的文件的名称,并在DirectoryNotEmptyException上执行try-catch。当这个异常发生时,捕获它并抛出指定文件的您自己的异常。
try {
Files.delete(file);
} catch (DirectoryNotEmptyException e) {
throw new MySpecificException(file.getFileName());
}
class MySpecificException extends Exception {
public MySpecificException() { }
public MySpecificException(string filename) {
super(filename);
}
}
Getting the filename can be done with e.getMessage();
获取文件名可以使用e.getMessage();
I assume Files.delete()
continues deleting the files in a directory when it encounters a file that can't be deleted. If this is the case, my method still works: just return a list of all the files in the directory instead of the filename of the directory. It should only contain files that can't be deleted and you still have your solution.
假设文件。delete()在遇到无法删除的文件时继续删除目录中的文件。如果是这种情况,我的方法仍然有效:只返回目录中所有文件的列表,而不是目录的文件名。它应该只包含不能删除的文件,并且您仍然拥有您的解决方案。
#4
-1
An expansion on my comment above.
我上面评论的扩展。
When Java has a problem doing something it throws an Exception. Exceptions, like all other types, can be inherited from. The API will specify which checked exceptions each method throws. The methods in java.io
and java.nio
will usually throw IOException
or one of it's children. If you want to create a method that will tell you why the file operation, in this case deletion, failed you can do something like this:
当Java做某事有问题时,它会抛出异常。与所有其他类型一样,异常也可以继承。API将指定每个方法抛出的检查异常。在java的方法。io和java。nio通常会抛出IOException或者它的一个子元素。如果您想要创建一个方法来告诉您为什么文件操作(在本例中是删除操作)失败,您可以这样做:
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
//You don't need to try to delete the file again since this method was called
//because the deletion failed. Instead...
if (exc instanceof NoSuchFileException) {
System.out.println("Could not find the file: " + file);
} else if (exc instanceof DirectoryNotEmptyException) {
System.out.println("The directory [+ " file + "] was not empty.");
} else {
System.out.println("Could not delete file [" + file
+ "]. There was a problem communicating with the file system");
}
return FileVisitResult.CONTINUE;
}
You can change the actual program response as you need to but that should give you the general idea.
您可以根据需要更改实际的程序响应,但这应该会给您一个大致的概念。