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]

Reply via email to