340949 Scanner delays file notifications until files are stable
git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2948 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
parent
548f6bac5a
commit
1bd0adabfb
|
@ -10,6 +10,7 @@ jetty-7.3.2-SNAPSHOT
|
||||||
+ 340838 Update ConnectHandler to perform half closes properly
|
+ 340838 Update ConnectHandler to perform half closes properly
|
||||||
+ 340878 Integrations should be able to load their own keystores
|
+ 340878 Integrations should be able to load their own keystores
|
||||||
+ 340920 Dynamically assign RMI registry port for integration testing
|
+ 340920 Dynamically assign RMI registry port for integration testing
|
||||||
|
+ 340949 Scanner delays file notifications until files are stable
|
||||||
+ 341006 Move inner enums out into separate file
|
+ 341006 Move inner enums out into separate file
|
||||||
+ 341105 Stack trace is printed for an ignored exception
|
+ 341105 Stack trace is printed for an ignored exception
|
||||||
+ 341145 WebAppContext MBean attribute serverClasses returns empty value
|
+ 341145 WebAppContext MBean attribute serverClasses returns empty value
|
||||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.client;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -60,7 +61,6 @@ public class ExpireTest
|
||||||
}
|
}
|
||||||
catch (InterruptedException x)
|
catch (InterruptedException x)
|
||||||
{
|
{
|
||||||
throw new ServletException(x);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,8 +13,8 @@ import org.junit.Test;
|
||||||
|
|
||||||
public class WebAppProviderTest
|
public class WebAppProviderTest
|
||||||
{
|
{
|
||||||
@Rule
|
@Rule
|
||||||
public TestingDir testdir = new TestingDir();
|
public TestingDir testdir = new TestingDir();
|
||||||
private static XmlConfiguredJetty jetty;
|
private static XmlConfiguredJetty jetty;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|
|
@ -40,7 +40,7 @@ public abstract class PolicyMonitor extends AbstractLifeCycle
|
||||||
/**
|
/**
|
||||||
* scan interval in seconds for policy file changes
|
* scan interval in seconds for policy file changes
|
||||||
*/
|
*/
|
||||||
private int _scanInterval = 10;
|
private int _scanInterval = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* specialized listener enabling waitForScan() functionality
|
* specialized listener enabling waitForScan() functionality
|
||||||
|
@ -72,6 +72,7 @@ public abstract class PolicyMonitor extends AbstractLifeCycle
|
||||||
*/
|
*/
|
||||||
public PolicyMonitor( String directory )
|
public PolicyMonitor( String directory )
|
||||||
{
|
{
|
||||||
|
this();
|
||||||
_policyDirectory = directory;
|
_policyDirectory = directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +145,8 @@ public abstract class PolicyMonitor extends AbstractLifeCycle
|
||||||
*/
|
*/
|
||||||
public synchronized void waitForScan() throws Exception
|
public synchronized void waitForScan() throws Exception
|
||||||
{
|
{
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
// wait for 2 scans for stable files
|
||||||
|
CountDownLatch latch = new CountDownLatch(2);
|
||||||
|
|
||||||
_scanningListener.setScanningLatch(latch);
|
_scanningListener.setScanningLatch(latch);
|
||||||
_scanner.scan();
|
_scanner.scan();
|
||||||
|
|
|
@ -36,6 +36,7 @@ public class PolicyMonitorTest
|
||||||
count.incrementAndGet();
|
count.incrementAndGet();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
monitor.setScanInterval(1);
|
||||||
|
|
||||||
monitor.start();
|
monitor.start();
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Set;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,16 +39,15 @@ import org.eclipse.jetty.util.log.Log;
|
||||||
* Utility for scanning a directory for added, removed and changed
|
* Utility for scanning a directory for added, removed and changed
|
||||||
* files and reporting these events via registered Listeners.
|
* files and reporting these events via registered Listeners.
|
||||||
*
|
*
|
||||||
* TODO AbstractLifeCycle
|
|
||||||
*/
|
*/
|
||||||
public class Scanner
|
public class Scanner extends AbstractLifeCycle
|
||||||
{
|
{
|
||||||
private static int __scannerId=0;
|
private static int __scannerId=0;
|
||||||
private int _scanInterval;
|
private int _scanInterval;
|
||||||
private int _scanCount = 0;
|
private int _scanCount = 0;
|
||||||
private final List<Listener> _listeners = new ArrayList<Listener>();
|
private final List<Listener> _listeners = new ArrayList<Listener>();
|
||||||
private final Map<String,Long> _prevScan = new HashMap<String,Long> ();
|
private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> ();
|
||||||
private final Map<String,Long> _currentScan = new HashMap<String,Long> ();
|
private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> ();
|
||||||
private FilenameFilter _filter;
|
private FilenameFilter _filter;
|
||||||
private final List<File> _scanDirs = new ArrayList<File>();
|
private final List<File> _scanDirs = new ArrayList<File>();
|
||||||
private volatile boolean _running = false;
|
private volatile boolean _running = false;
|
||||||
|
@ -56,8 +56,45 @@ public class Scanner
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
private TimerTask _task;
|
private TimerTask _task;
|
||||||
private int _scanDepth=0;
|
private int _scanDepth=0;
|
||||||
|
|
||||||
|
public enum Notification { ADDED, CHANGED, REMOVED };
|
||||||
|
private final Map<String,Notification> _notifications = new HashMap<String,Notification>();
|
||||||
|
|
||||||
|
static class TimeNSize
|
||||||
|
{
|
||||||
|
final long _lastModified;
|
||||||
|
final long _size;
|
||||||
|
|
||||||
|
public TimeNSize(long lastModified, long size)
|
||||||
|
{
|
||||||
|
_lastModified = lastModified;
|
||||||
|
_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return (int)_lastModified^(int)_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (o instanceof TimeNSize)
|
||||||
|
{
|
||||||
|
TimeNSize tns = (TimeNSize)o;
|
||||||
|
return tns._lastModified==_lastModified && tns._size==_size;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "[lm="+_lastModified+",s="+_size+"]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener
|
* Listener
|
||||||
*
|
*
|
||||||
|
@ -274,7 +311,7 @@ public class Scanner
|
||||||
/**
|
/**
|
||||||
* Start the scanning action.
|
* Start the scanning action.
|
||||||
*/
|
*/
|
||||||
public synchronized void start ()
|
public synchronized void doStart()
|
||||||
{
|
{
|
||||||
if (_running)
|
if (_running)
|
||||||
return;
|
return;
|
||||||
|
@ -285,6 +322,7 @@ public class Scanner
|
||||||
{
|
{
|
||||||
// if files exist at startup, report them
|
// if files exist at startup, report them
|
||||||
scan();
|
scan();
|
||||||
|
scan(); // scan twice so files reported as stable
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -321,14 +359,14 @@ public class Scanner
|
||||||
{
|
{
|
||||||
_timer = newTimer();
|
_timer = newTimer();
|
||||||
_task = newTimerTask();
|
_task = newTimerTask();
|
||||||
_timer.schedule(_task, 1000L*getScanInterval(),1000L*getScanInterval());
|
_timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Stop the scanning.
|
* Stop the scanning.
|
||||||
*/
|
*/
|
||||||
public synchronized void stop ()
|
public synchronized void doStop()
|
||||||
{
|
{
|
||||||
if (_running)
|
if (_running)
|
||||||
{
|
{
|
||||||
|
@ -388,45 +426,97 @@ public class Scanner
|
||||||
* @param currentScan the info from the most recent pass
|
* @param currentScan the info from the most recent pass
|
||||||
* @param oldScan info from the previous pass
|
* @param oldScan info from the previous pass
|
||||||
*/
|
*/
|
||||||
public void reportDifferences (Map<String,Long> currentScan, Map<String,Long> oldScan)
|
public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan)
|
||||||
{
|
{
|
||||||
List<String> bulkChanges = new ArrayList<String>();
|
// scan the differences and add what was found to the map of notifications:
|
||||||
|
|
||||||
Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
|
Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
|
||||||
Iterator<Entry<String, Long>> itor = currentScan.entrySet().iterator();
|
|
||||||
while (itor.hasNext())
|
// Look for new and changed files
|
||||||
|
for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet())
|
||||||
{
|
{
|
||||||
Map.Entry<String, Long> entry = itor.next();
|
String file = entry.getKey();
|
||||||
if (!oldScanKeys.contains(entry.getKey()))
|
if (!oldScanKeys.contains(file))
|
||||||
{
|
{
|
||||||
Log.debug("File added: "+entry.getKey());
|
Notification old=_notifications.put(file,Notification.ADDED);
|
||||||
reportAddition ((String)entry.getKey());
|
if (old!=null)
|
||||||
bulkChanges.add(entry.getKey());
|
{
|
||||||
|
switch(old)
|
||||||
|
{
|
||||||
|
case REMOVED:
|
||||||
|
case CHANGED:
|
||||||
|
_notifications.put(file,Notification.CHANGED);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!oldScan.get(entry.getKey()).equals(entry.getValue()))
|
else if (!oldScan.get(file).equals(currentScan.get(file)))
|
||||||
{
|
{
|
||||||
Log.debug("File changed: "+entry.getKey());
|
Notification old=_notifications.put(file,Notification.CHANGED);
|
||||||
reportChange((String)entry.getKey());
|
if (old!=null)
|
||||||
oldScanKeys.remove(entry.getKey());
|
{
|
||||||
bulkChanges.add(entry.getKey());
|
switch(old)
|
||||||
}
|
{
|
||||||
else
|
case ADDED:
|
||||||
oldScanKeys.remove(entry.getKey());
|
_notifications.put(file,Notification.ADDED);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!oldScanKeys.isEmpty())
|
|
||||||
{
|
|
||||||
|
|
||||||
Iterator<String> keyItor = oldScanKeys.iterator();
|
|
||||||
while (keyItor.hasNext())
|
|
||||||
{
|
|
||||||
String filename = (String)keyItor.next();
|
|
||||||
Log.debug("File removed: "+filename);
|
|
||||||
reportRemoval(filename);
|
|
||||||
bulkChanges.add(filename);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look for deleted files
|
||||||
|
for (String file : oldScan.keySet())
|
||||||
|
{
|
||||||
|
if (!currentScan.containsKey(file))
|
||||||
|
{
|
||||||
|
Notification old=_notifications.put(file,Notification.REMOVED);
|
||||||
|
if (old!=null)
|
||||||
|
{
|
||||||
|
switch(old)
|
||||||
|
{
|
||||||
|
case ADDED:
|
||||||
|
_notifications.remove(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Log.isDebugEnabled())
|
||||||
|
Log.debug("scanned "+_notifications);
|
||||||
|
|
||||||
|
// Process notifications
|
||||||
|
// Only process notifications that are for stable files (ie same in old and current scan).
|
||||||
|
List<String> bulkChanges = new ArrayList<String>();
|
||||||
|
for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();)
|
||||||
|
{
|
||||||
|
Entry<String,Notification> entry=iter.next();
|
||||||
|
String file=entry.getKey();
|
||||||
|
|
||||||
|
// Is the file stable?
|
||||||
|
if (oldScan.containsKey(file))
|
||||||
|
{
|
||||||
|
if (!oldScan.get(file).equals(currentScan.get(file)))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (currentScan.containsKey(file))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// File is stable so notify
|
||||||
|
Notification notification=entry.getValue();
|
||||||
|
iter.remove();
|
||||||
|
bulkChanges.add(file);
|
||||||
|
switch(notification)
|
||||||
|
{
|
||||||
|
case ADDED:
|
||||||
|
reportAddition(file);
|
||||||
|
break;
|
||||||
|
case CHANGED:
|
||||||
|
reportChange(file);
|
||||||
|
break;
|
||||||
|
case REMOVED:
|
||||||
|
reportRemoval(file);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!bulkChanges.isEmpty())
|
if (!bulkChanges.isEmpty())
|
||||||
reportBulkChanges(bulkChanges);
|
reportBulkChanges(bulkChanges);
|
||||||
}
|
}
|
||||||
|
@ -438,7 +528,7 @@ public class Scanner
|
||||||
* @param f file or directory
|
* @param f file or directory
|
||||||
* @param scanInfoMap map of filenames to last modified times
|
* @param scanInfoMap map of filenames to last modified times
|
||||||
*/
|
*/
|
||||||
private void scanFile (File f, Map<String,Long> scanInfoMap, int depth)
|
private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -450,8 +540,7 @@ public class Scanner
|
||||||
if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
|
if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
|
||||||
{
|
{
|
||||||
String name = f.getCanonicalPath();
|
String name = f.getCanonicalPath();
|
||||||
long lastModified = f.lastModified();
|
scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length()));
|
||||||
scanInfoMap.put(name, new Long(lastModified));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue