Index: src/main/java/org/jvnet/mimepull/MemoryData.java =================================================================== --- src/main/java/org/jvnet/mimepull/MemoryData.java (revision 213) +++ src/main/java/org/jvnet/mimepull/MemoryData.java (working copy) @@ -94,8 +94,7 @@ String prefix = config.getTempFilePrefix(); String suffix = config.getTempFileSuffix(); File tempFile = TempFiles.createTempFile(prefix, suffix, config.getTempDir()); - // delete the temp file when VM exits as a last resort for file clean up - tempFile.deleteOnExit(); + TempFileCleaner.register(tempFile); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Created temp file = {0}", tempFile); } Index: src/main/java/org/jvnet/mimepull/TempFileCleaner.java =================================================================== --- src/main/java/org/jvnet/mimepull/TempFileCleaner.java (revision 0) +++ src/main/java/org/jvnet/mimepull/TempFileCleaner.java (revision 0) @@ -0,0 +1,201 @@ +package org.jvnet.mimepull; + +import java.io.File; +import java.util.ArrayList; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +class TempFileCleaner { + + private static final Logger LOGGER = Logger.getLogger(TempFileCleaner.class.getName()); + private static final String CLEANER_TIMER_NAME = "mimepull-cleaner-timer"; + private static final int DEFAULT_INTERVAL = 10; // minutes + private static final int MAX_COUNT = 5; + + private static final TempFileCleaner instance = new TempFileCleaner(); + private static final ScheduledExecutorService scheduler; + private ConcurrentHashMap fileMap; + + static { + int interval = DEFAULT_INTERVAL; + try { + interval = Integer.getInteger("org.jvnet.mimepull.interval", DEFAULT_INTERVAL); + } catch (SecurityException se) { + if (LOGGER.isLoggable(Level.CONFIG)) { + LOGGER.log(Level.CONFIG, + "Cannot read ''{0}'' property, using defaults.", + new Object[] {"org.jvnet.mimepull.interval"}); + } + } + + ThreadFactory factory = new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName(CLEANER_TIMER_NAME); + t.setDaemon(true); + return t; + } + }; + scheduler = Executors.newScheduledThreadPool(1, factory); + scheduler.scheduleWithFixedDelay(new Cleaner(), interval, interval, TimeUnit.MINUTES); + LOGGER.log(Level.FINE, "Created temporary file cleaner for MIMEPULL"); + } + + /* Private constructor. */ + private TempFileCleaner() { + this.fileMap = new ConcurrentHashMap(); + + Thread hook = new Thread(new ShutdownHook()); + Runtime.getRuntime().addShutdownHook(hook); + } + + private synchronized int countup(File f) { + Integer count = instance.fileMap.get(f); + if (count == null) { + count = Integer.MIN_VALUE; + }else { + ++count; + instance.fileMap.put(f, count); + } + return count.intValue(); + } + + public static void register(File f) { + instance.fileMap.put(f, -1); + } + + public static boolean delete(File f) { + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.log(Level.FINER, "Deleting file = {0}", f.getName()); + } + + boolean deleted = false;; + try { + deleted = f.delete(); + } catch (SecurityException se) { + LOGGER.log(Level.WARNING, null, se); + } + + if (deleted) { + // remove fileMap entry + instance.fileMap.remove(f); + } else { + // count up + Integer count = instance.fileMap.get(f); + if (count != null) { + ++count; + instance.fileMap.put(f, count); + } + } + + return deleted; + } + + static class Cleaner implements Runnable { + @Override + public void run() { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "Cleaning start."); + } + + int removedCount = 0; + if (!instance.fileMap.isEmpty()) { + ArrayList list = new ArrayList(); + + Set> entries = instance.fileMap.entrySet(); + for (Entry entry : entries) { + int count = entry.getValue().intValue(); + if (count < 0) { + continue; + } + + File file = entry.getKey(); + if (!file.exists()) { + list.add(file); + continue; + } + + // try to delete file + try { + if (file.delete()) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "Deleting file = {0}", file.getName()); + } + ++removedCount; + list.add(file); + continue; + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, null, ex); + } + + // give up deletion by this Cleaner + count = instance.countup(file); + if (MAX_COUNT < count) { + file.deleteOnExit(); + list.add(file); + } + } + entries.clear(); + + // remove deleted file entries + for (File deletedFile : list) { + instance.fileMap.remove(deletedFile); + } + } + + if (LOGGER.isLoggable(Level.FINE)) { + StringBuilder msg = new StringBuilder(); + msg.append("Cleaning finished"); + if (removedCount >= 0) { + msg.append(" "); + msg.append(removedCount); + msg.append(" files removed."); + } else { + msg.append("."); + } + LOGGER.log(Level.FINE, msg.toString()); + } + } + } + + static class ShutdownHook implements Runnable { + @Override + public void run() { + int size = instance.fileMap.size(); + if (size > 0) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "files = " + size); + } + + Set files = instance.fileMap.keySet(); + for (File file : files) { + if (file.exists()) { + boolean deleted = false; + try { + deleted = file.delete(); + } catch (Exception ex) { + LOGGER.log(Level.WARNING, null, ex); + } + + // give up deletion by this Cleaner + if (!deleted) { + file.deleteOnExit(); + } + } + } + instance.fileMap.clear(); + } + scheduler.shutdown(); + } + } + +} Index: src/main/java/org/jvnet/mimepull/WeakDataFile.java =================================================================== --- src/main/java/org/jvnet/mimepull/WeakDataFile.java (revision 213) +++ src/main/java/org/jvnet/mimepull/WeakDataFile.java (working copy) @@ -130,7 +130,7 @@ refList.remove(this); try { raf.close(); - boolean deleted = file.delete(); + boolean deleted = TempFileCleaner.delete(file); if (!deleted) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, "File {0} was not deleted", file.getAbsolutePath());