Author: scolebourne
Date: Mon Dec 4 17:13:05 2006
New Revision: 482437
URL: http://svn.apache.org/viewvc?view=rev&rev=482437
Log:
IO-99 - FileCleaner.exitWhenFinished, to allow the thread to be terminated
includes some code from Jochen Wiedmann
Modified:
jakarta/commons/proper/io/trunk/RELEASE-NOTES.txt
jakarta/commons/proper/io/trunk/project.xml
jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/FileCleaner.java
jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/FileCleanerTestCase.java
Modified: jakarta/commons/proper/io/trunk/RELEASE-NOTES.txt
URL:
http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk/RELEASE-NOTES.txt?view=diff&rev=482437&r1=482436&r2=482437
==============================================================================
--- jakarta/commons/proper/io/trunk/RELEASE-NOTES.txt (original)
+++ jakarta/commons/proper/io/trunk/RELEASE-NOTES.txt Mon Dec 4 17:13:05 2006
@@ -110,6 +110,9 @@
- This can be used as a calback in FileCleaner
- Together these allow FileCleaner to do a forceDelete to kill directories
+- FileCleaner.exitWhenFinished [IO-99]
+ - A new method that allows the internal cleaner thread to be cleanly
terminated
+
- WildcardFileFilter
- Replacement for WildcardFilter
- Accepts both files and directories
Modified: jakarta/commons/proper/io/trunk/project.xml
URL:
http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk/project.xml?view=diff&rev=482437&r1=482436&r2=482437
==============================================================================
--- jakarta/commons/proper/io/trunk/project.xml (original)
+++ jakarta/commons/proper/io/trunk/project.xml Mon Dec 4 17:13:05 2006
@@ -224,6 +224,9 @@
<name>James Urie</name>
</contributor>
<contributor>
+ <name>Jochen Wiedmann</name>
+ </contributor>
+ <contributor>
<name>Frank W. Zammetti</name>
</contributor>
</contributors>
Modified:
jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/FileCleaner.java
URL:
http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/FileCleaner.java?view=diff&rev=482437&r1=482436&r2=482437
==============================================================================
---
jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/FileCleaner.java
(original)
+++
jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/FileCleaner.java
Mon Dec 4 17:13:05 2006
@@ -29,6 +29,12 @@
* This utility creates a background thread to handle file deletion.
* Each file to be deleted is registered with a handler object.
* When the handler object is garbage collected, the file is deleted.
+ * <p>
+ * In an environment with multiple class loaders (a servlet container, for
+ * example), you should consider stopping the background thread if it is no
+ * longer needed. This is done by invoking the method
+ * [EMAIL PROTECTED] [EMAIL PROTECTED] #exitWhenFinished}, typically in
+ * [EMAIL PROTECTED] javax.servlet.ServletContextListener#contextDestroyed} or
similar.
*
* @author Noel Bergman
* @author Martin Cooper
@@ -39,47 +45,19 @@
/**
* Queue of <code>Tracker</code> instances being watched.
*/
- private static ReferenceQueue /* Tracker */ q = new ReferenceQueue();
-
+ static ReferenceQueue /* Tracker */ q = new ReferenceQueue();
/**
* Collection of <code>Tracker</code> instances in existence.
*/
- private static Collection /* Tracker */ trackers = new Vector();
-
+ static Collection /* Tracker */ trackers = new Vector();
/**
- * The thread that will clean up registered files.
+ * Whether to terminate the thread when the tracking is complete.
*/
- private static Thread reaper = new Thread("File Reaper") {
-
- /**
- * Run the reaper thread that will delete files as their associated
- * marker objects are reclaimed by the garbage collector.
- */
- public void run() {
- for (;;) {
- Tracker tracker = null;
- try {
- // Wait for a tracker to remove.
- tracker = (Tracker) q.remove();
- } catch (Exception e) {
- continue;
- }
-
- tracker.delete();
- tracker.clear();
- trackers.remove(tracker);
- }
- }
- };
-
+ static volatile boolean exitWhenFinished = false;
/**
- * The static initializer that starts the reaper thread.
+ * The thread that will clean up registered files.
*/
- static {
- reaper.setPriority(Thread.MAX_PRIORITY);
- reaper.setDaemon(true);
- reaper.start();
- }
+ static Thread reaper;
//-----------------------------------------------------------------------
/**
@@ -109,7 +87,7 @@
if (file == null) {
throw new NullPointerException("The file must not be null");
}
- trackers.add(new Tracker(file.getPath(), deleteStrategy, marker, q));
+ addTracker(file.getPath(), marker, deleteStrategy);
}
/**
@@ -139,9 +117,28 @@
if (path == null) {
throw new NullPointerException("The path must not be null");
}
+ addTracker(path, marker, deleteStrategy);
+ }
+
+ /**
+ * Adds a tracker to the list of trackers.
+ *
+ * @param path the full path to the file to be tracked, not null
+ * @param marker the marker object used to track the file, not null
+ * @param deleteStrategy the strategy to delete the file, null means
normal
+ */
+ private static synchronized void addTracker(String path, Object marker,
FileDeleteStrategy deleteStrategy) {
+ if (exitWhenFinished) {
+ throw new IllegalStateException("No new trackers can be added once
exitWhenFinished() is called");
+ }
+ if (reaper == null) {
+ reaper = new Reaper();
+ reaper.start();
+ }
trackers.add(new Tracker(path, deleteStrategy, marker, q));
}
+ //-----------------------------------------------------------------------
/**
* Retrieve the number of files currently being tracked, and therefore
* awaiting deletion.
@@ -152,11 +149,75 @@
return trackers.size();
}
+ /**
+ * Call this method to cause the file cleaner thread to terminate when
+ * there are no more objects being tracked for deletion.
+ * <p>
+ * In a simple environment, you don't need this method as the file cleaner
+ * thread will simply exit when the JVM exits. In a more complex
environment,
+ * with multiple class loaders (such as an application server), you should
be
+ * aware that the file cleaner thread will continue running even if the
class
+ * loader it was started from terminates. This can consitute a memory leak.
+ * <p>
+ * For example, suppose that you have developed a web application, which
+ * contains the commons-io jar file in your WEB-INF/lib directory. In other
+ * words, the FileCleaner class is loaded through the class loader of your
+ * web application. If the web application is terminated, but the servlet
+ * container is still running, then the file cleaner thread will still
exist,
+ * posing a memory leak.
+ * <p>
+ * This method allows the thread to be terminated. Simply call this method
+ * in the resource cleanup code, such as [EMAIL PROTECTED]
javax.servlet.ServletContextListener#contextDestroyed}.
+ * One called, no new objects can be tracked by the file cleaner.
+ */
+ public static synchronized void exitWhenFinished() {
+ exitWhenFinished = true;
+ if (reaper != null) {
+ synchronized (reaper) {
+ reaper.interrupt();
+ }
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * The reaper thread.
+ */
+ static final class Reaper extends Thread {
+ Reaper() {
+ super("File Reaper");
+ setPriority(Thread.MAX_PRIORITY);
+ setDaemon(true);
+ }
+
+ /**
+ * Run the reaper thread that will delete files as their associated
+ * marker objects are reclaimed by the garbage collector.
+ */
+ public void run() {
+ // thread exits when exitWhenFinished is true and there are no
more tracked objects
+ while (exitWhenFinished == false || trackers.size() > 0) {
+ Tracker tracker = null;
+ try {
+ // Wait for a tracker to remove.
+ tracker = (Tracker) q.remove();
+ } catch (Exception e) {
+ continue;
+ }
+ if (tracker != null) {
+ tracker.delete();
+ tracker.clear();
+ trackers.remove(tracker);
+ }
+ }
+ }
+ }
+
//-----------------------------------------------------------------------
/**
* Inner class which acts as the reference for a file pending deletion.
*/
- static class Tracker extends PhantomReference {
+ static final class Tracker extends PhantomReference {
/**
* The full path to the file being tracked.
Modified:
jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/FileCleanerTestCase.java
URL:
http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/FileCleanerTestCase.java?view=diff&rev=482437&r1=482436&r2=482437
==============================================================================
---
jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/FileCleanerTestCase.java
(original)
+++
jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/FileCleanerTestCase.java
Mon Dec 4 17:13:05 2006
@@ -19,6 +19,8 @@
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.lang.ref.ReferenceQueue;
+import java.util.Vector;
import junit.framework.Test;
import junit.framework.TestSuite;
@@ -62,6 +64,12 @@
/** @see junit.framework.TestCase#tearDown() */
protected void tearDown() throws Exception {
FileUtils.deleteDirectory(getTestDirectory());
+
+ // reset file cleaner class, so as not to break other tests
+ FileCleaner.q = new ReferenceQueue();
+ FileCleaner.trackers = new Vector();
+ FileCleaner.exitWhenFinished = false;
+ FileCleaner.reaper = null;
}
//-----------------------------------------------------------------------
@@ -170,6 +178,101 @@
}
}
+ public void testFileCleanerExitWhenFinishedFirst() throws Exception {
+ assertEquals(false, FileCleaner.exitWhenFinished);
+ FileCleaner.exitWhenFinished();
+ assertEquals(true, FileCleaner.exitWhenFinished);
+ assertEquals(null, FileCleaner.reaper);
+
+ waitUntilTrackCount();
+
+ assertEquals(0, FileCleaner.getTrackCount());
+ assertEquals(true, FileCleaner.exitWhenFinished);
+ assertEquals(null, FileCleaner.reaper);
+ }
+
+ public void testFileCleanerExitWhenFinished_NoTrackAfter() throws
Exception {
+ assertEquals(false, FileCleaner.exitWhenFinished);
+ FileCleaner.exitWhenFinished();
+ assertEquals(true, FileCleaner.exitWhenFinished);
+ assertEquals(null, FileCleaner.reaper);
+
+ String path = testFile.getPath();
+ Object marker = new Object();
+ try {
+ FileCleaner.track(path, marker);
+ fail();
+ } catch (IllegalStateException ex) {
+ // expected
+ }
+ assertEquals(true, FileCleaner.exitWhenFinished);
+ assertEquals(null, FileCleaner.reaper);
+ }
+
+ public void testFileCleanerExitWhenFinished1() throws Exception {
+ String path = testFile.getPath();
+
+ assertEquals(false, testFile.exists());
+ RandomAccessFile r = new RandomAccessFile(testFile, "rw");
+ assertEquals(true, testFile.exists());
+
+ assertEquals(0, FileCleaner.getTrackCount());
+ FileCleaner.track(path, r);
+ assertEquals(1, FileCleaner.getTrackCount());
+ assertEquals(false, FileCleaner.exitWhenFinished);
+ assertEquals(true, FileCleaner.reaper.isAlive());
+
+ assertEquals(false, FileCleaner.exitWhenFinished);
+ FileCleaner.exitWhenFinished();
+ assertEquals(true, FileCleaner.exitWhenFinished);
+ assertEquals(true, FileCleaner.reaper.isAlive());
+
+ r.close();
+ testFile = null;
+ r = null;
+
+ waitUntilTrackCount();
+
+ assertEquals(0, FileCleaner.getTrackCount());
+ assertEquals(false, new File(path).exists());
+ assertEquals(true, FileCleaner.exitWhenFinished);
+ assertEquals(false, FileCleaner.reaper.isAlive());
+ }
+
+ public void testFileCleanerExitWhenFinished2() throws Exception {
+ String path = testFile.getPath();
+
+ assertEquals(false, testFile.exists());
+ RandomAccessFile r = new RandomAccessFile(testFile, "rw");
+ assertEquals(true, testFile.exists());
+
+ assertEquals(0, FileCleaner.getTrackCount());
+ FileCleaner.track(path, r);
+ assertEquals(1, FileCleaner.getTrackCount());
+ assertEquals(false, FileCleaner.exitWhenFinished);
+ assertEquals(true, FileCleaner.reaper.isAlive());
+
+ r.close();
+ testFile = null;
+ r = null;
+
+ waitUntilTrackCount();
+
+ assertEquals(0, FileCleaner.getTrackCount());
+ assertEquals(false, new File(path).exists());
+ assertEquals(false, FileCleaner.exitWhenFinished);
+ assertEquals(true, FileCleaner.reaper.isAlive());
+
+ assertEquals(false, FileCleaner.exitWhenFinished);
+ FileCleaner.exitWhenFinished();
+ for (int i = 0; i < 20 && FileCleaner.reaper.isAlive(); i++) {
+ Thread.sleep(500L); // allow reaper thread to die
+ }
+ assertEquals(true, FileCleaner.exitWhenFinished);
+ assertEquals(false, FileCleaner.reaper.isAlive());
+ }
+
+ //-----------------------------------------------------------------------
private void waitUntilTrackCount() {
while (FileCleaner.getTrackCount() != 0) {
int total = 0;
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]