Interesting code: org.codehaus.plexus.util.FileUtils.forceDelete(File)

Interesting code: Today we have a look at org.codehaus.plexus.util.FileUtils.forceDelete(File).

FileUtils.foreceDelete is a very common helper function, found in nearly all util libs around the world. Why is this one from the plexus-utils project so interesting to have a detailed look into it? Short answer: it turns nearly all my Eclipse full builds into a CPU consuming long running task.

As mentioned forceDelete is very common and if you have look into the Appache commons-io project you will found the nearly same function — but a little bit different.

forceDelete in the commons-io

This code is from Appache commons-io:2.5 (newest version):

[code lang=java]
public static void forceDelete(File file) throws IOException {
if (file.isDirectory()) {
deleteDirectory(file);
} else {
boolean filePresent = file.exists();
if (!file.delete()) {
if (!filePresent){
throw new FileNotFoundException("File does not exist: " + file);
}
String message =
"Unable to delete file: " + file;
throw new IOException(message);
}
}
}
[/code]

forceDelete in the plexus-utils

An the next code is from the plexus-utils:3.0.24 project (newest version).

[code lang=java]
public static void forceDelete( final File file ) throws IOException {
if ( file.isDirectory() ) {
deleteDirectory( file );
} else {
/* NOTE: Always try to delete the file even if it appears to be non-existent.
* This will ensure that a symlink whose target does not exist is deleted, too. */
boolean filePresent = file.getCanonicalFile().exists();
if ( !deleteFile( file ) && filePresent ) {
final String message = "File " + file + " unable to be deleted.";
throw new IOException( message );
}
}
}
[/code]

On the first view both do the same:
* first check if the given file is a directory,
* second check if the file exists
* third delete the file
* last throw an exception, if the file could not be deleted.

But the plexus version version does not call the file.delete method, it calls a private delete(File) method. So we have a look at this method:

[code lang=java]
private static boolean deleteFile( File file ) throws IOException {
if ( file.isDirectory() ) {
throw new IOException( "File " + file + " isn't a file." );
}
if ( !file.delete() ) {
if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) {
file = file.getCanonicalFile();
System.gc();
}
try {
Thread.sleep( 10 );
return file.delete();
} catch ( InterruptedException ignore ) {
return file.delete();
}
}
return true;
}
[/code]

This method does a lot of magic in case the file could not be deleted:
* first it starts a Garbage-Collection on a Windows system
* second it sleeps for 10 millis
* last: it just tries the delete a second time

Currently I have no idea, why a Garbage-Collection run and to wait 10 millis will change the result for the file.delete on any Windows-Systems. In case the file was opened by an other application we can nothing do in our application to solve the situation. If the file was opened in our own application, I suggest to just close the file.

In the past I came around a lot of situations, where we could not delete a file in our application and in all those situations it was always the same reason: some one did not close the stream and it was always a problem on Windows-Systems — we choose to fix the code to close the stream and get every file file.delete() deleted with any other magic 🙂 .

I assume the version of commons-io will just du the right things and suggest to remove all the additional stuff from the `plexis-utils‘ version.

forceDelete in the yuicompressor-maven-plugin

If you call this method for one or two files you will not be aware of the System.gc() call and of the 10 millis sleeping — but if your application used already 2 GB memory and you call it 1.000 or more times your application drastically slows down and the garbage collecter will consume 100% CPU.

This happened in the yuicompressor-maven-plugin, if you have a lot of *.css files and do your development with Eclipse on a Windows-System (Team members on Linux system did not have any problems.) — I assume it will slow down every IDE which uses this plugin.

Now we will have a look at the yuicompressor-maven-plugin code:

[code lang=java]
@Override
protected void processFile(SourceFile src) throws Exception {

InputStreamReader in = null;
OutputStreamWriter out = null;
File outFileTmp = new File(outFile.getAbsolutePath() + ".tmp");
FileUtils.forceDelete(outFileTmp);
try {
in = new InputStreamReader(new FileInputStream(inFile), encoding);

[/code]

The {{forceDelete}} is used to just make sure to have no old tmp files laying around. I suggest to just use a ‚better‘ forceDelete implementation or to put a file.exists before the forceDelete to workaround the „Windows-Problems“ of the original FileUtils.foreceDelete.

In my environment I just disable the yuicompressor-maven-plugin as quick fix and second I opend a bug on github and looking forward what happend. Stay tuned.

(1) https://github.com/codehaus-plexus/plexus-utils/issues/21

Schreibe einen Kommentar