Jetty 9.4.x 2038 slow filesessiondatastore (#2102)

* Issue #2038 Speed up FileSessionDataStore

Signed-off-by: Jan Bartel <janb@webtide.com>
This commit is contained in:
Jan Bartel 2018-01-10 16:40:53 +01:00 committed by GitHub
parent 356bf2e06f
commit a0b8321ef4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 553 additions and 320 deletions

View File

@ -24,22 +24,27 @@ import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@ -54,11 +59,21 @@ import org.eclipse.jetty.util.log.Logger;
@ManagedObject
public class FileSessionDataStore extends AbstractSessionDataStore
{
private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
private File _storeDir;
private boolean _deleteUnrestorableFiles = false;
private Map<String,String> _sessionFileMap = new ConcurrentHashMap<>();
private String _contextString;
protected long _lastSweepTime = 0L;
@Override
public void initialize(SessionContext context) throws Exception
{
super.initialize(context);
_contextString = _context.getCanonicalContextPath()+"_"+_context.getVhost();
}
@Override
protected void doStart() throws Exception
@ -70,6 +85,8 @@ public class FileSessionDataStore extends AbstractSessionDataStore
@Override
protected void doStop() throws Exception
{
_sessionFileMap.clear();
_lastSweepTime = 0;
super.doStop();
}
@ -99,27 +116,53 @@ public class FileSessionDataStore extends AbstractSessionDataStore
/**
* @see org.eclipse.jetty.server.session.SessionDataStore#delete(java.lang.String)
* Delete a session
*
* @param id session id
*/
@Override
public boolean delete(String id) throws Exception
{
File file = null;
if (_storeDir != null)
{
file = getFile(_storeDir, id);
if (file != null && file.exists() && file.getParentFile().equals(_storeDir))
{
return file.delete();
}
//remove from our map
String filename = _sessionFileMap.remove(getIdWithContext(id));
if (filename == null)
return false;
//remove the file
return deleteFile(filename);
}
return false;
}
/**
* @see org.eclipse.jetty.server.session.SessionDataStore#getExpired(Set)
* Delete the file associated with a session
*
* @param filename name of the file containing the session's information
*
* @return true if file was deleted, false otherwise
* @throws Exception
*/
public boolean deleteFile (String filename) throws Exception
{
if (filename == null)
return false;
File file = new File(_storeDir, filename);
return Files.deleteIfExists(file.toPath());
}
/**
* Check to see which sessions have expired.
*
* @param candidates the set of session ids that the SessionCache believes
* have expired
* @return the complete set of sessions that have expired, including those
* that are not currently loaded into the SessionCache
*/
@Override
public Set<String> doGetExpired(final Set<String> candidates)
@ -127,89 +170,108 @@ public class FileSessionDataStore extends AbstractSessionDataStore
final long now = System.currentTimeMillis();
HashSet<String> expired = new HashSet<String>();
HashSet<String> idsWithContext = new HashSet<>();
//one pass to get all idWithContext
File [] files = _storeDir.listFiles(new FilenameFilter()
//iterate over the files and work out which have expired
for (String filename:_sessionFileMap.values())
{
@Override
public boolean accept(File dir, String name)
{
if (dir != _storeDir)
return false;
//dir may contain files that don't match our naming pattern
if (!match(name))
{
return false;
}
String idWithContext = getIdWithContextFromString(name);
if (!StringUtil.isBlank(idWithContext))
idsWithContext.add(idWithContext);
return true;
}
});
//got the list of all sessionids with their contexts, remove all old files for each one
for (String idWithContext:idsWithContext)
{
deleteOldFiles(_storeDir, idWithContext);
}
//now find sessions that have expired in any context
files = _storeDir.listFiles(new FilenameFilter()
{
@Override
public boolean accept(File dir, String name)
{
if (dir != _storeDir)
return false;
//dir may contain files that don't match our naming pattern
if (!match(name))
return false;
try
{
long expiry = getExpiryFromString(name);
return expiry > 0 && expiry < now;
long expiry = getExpiryFromFilename(filename);
if (expiry > 0 && expiry < now)
expired.add(getIdFromFilename(filename));
}
catch (Exception e)
{
return false;
}
}
});
if (files != null)
{
for (File f:files)
{
expired.add(getIdFromFile(f));
LOG.warn(e);
}
}
//check candidates that were not found to be expired, perhaps they no
//longer exist and they should be expired
//check candidates that were not found to be expired, perhaps
//because they no longer exist and they should be expired
for (String c:candidates)
{
if (!expired.contains(c))
{
//check if the file exists
File f = getFile(_storeDir, c);
if (f == null || !f.exists())
//if it doesn't have a file then the session doesn't exist
String filename = _sessionFileMap.get(getIdWithContext(c));
if (filename == null)
expired.add(c);
}
}
//Infrequently iterate over all files in the store, and delete those
//that expired a long time ago, even if they belong to
//another context. This ensures that files that
//belong to defunct contexts are cleaned up.
//If the graceperiod is disabled, don't do the sweep!
if ((_gracePeriodSec > 0) && ((_lastSweepTime == 0) || ((now - _lastSweepTime) >= (5*TimeUnit.SECONDS.toMillis(_gracePeriodSec)))))
{
_lastSweepTime = now;
sweepDisk();
}
return expired;
}
/**
* Check all session files that do not belong to this context and
* remove any that expired long ago (ie at least 5 gracePeriods ago).
*/
public void sweepDisk()
{
//iterate over the files in the store dir and check expiry times
long now = System.currentTimeMillis();
if (LOG.isDebugEnabled()) LOG.debug("Sweeping {} for old session files", _storeDir);
try
{
Files.walk(_storeDir.toPath(), 1, FileVisitOption.FOLLOW_LINKS)
.filter(p->!Files.isDirectory(p)).filter(p->!isOurContextSessionFilename(p.getFileName().toString()))
.filter(p->isSessionFilename(p.getFileName().toString()))
.forEach(p->{
try
{
sweepFile(now, p);
}
catch (Exception e)
{
LOG.warn(e);
}
});
}
catch (Exception e)
{
LOG.warn(e);
}
}
/**
* Check to see if the expiry on the file is very old, and
* delete the file if so. "Old" means that it expired at least
* 5 gracePeriods ago. The session can belong to any context.
*
* @param now the time now in msec
* @param p the file to check
*
* @throws Exception
*/
public void sweepFile (long now, Path p)
throws Exception
{
if (p == null)
return;
long expiry = getExpiryFromFilename(p.getFileName().toString());
//files with 0 expiry never expire
if (expiry >0 && ((now - expiry) >= (5*TimeUnit.SECONDS.toMillis(_gracePeriodSec))))
{
Files.deleteIfExists(p);
if (LOG.isDebugEnabled()) LOG.debug("Sweep deleted {}", p.getFileName());
}
}
/**
* @see org.eclipse.jetty.server.session.SessionDataStore#load(java.lang.String)
@ -223,13 +285,20 @@ public class FileSessionDataStore extends AbstractSessionDataStore
{
public void run ()
{
//get rid of all but the newest file for a session
File file = deleteOldFiles(_storeDir, getIdWithContext(id));
if (file == null || !file.exists())
//load session info from its file
String idWithContext = getIdWithContext(id);
String filename = _sessionFileMap.get(idWithContext);
if (filename == null)
{
if (LOG.isDebugEnabled())
LOG.debug("No file: {}",file);
LOG.debug("Unknown file {}",filename);
return;
}
File file = new File (_storeDir, filename);
if (!file.exists())
{
if (LOG.isDebugEnabled())
LOG.debug("No such file {}",filename);
return;
}
@ -243,9 +312,16 @@ public class FileSessionDataStore extends AbstractSessionDataStore
{
if (isDeleteUnrestorableFiles() && file.exists() && file.getParentFile().equals(_storeDir))
{
file.delete();
try
{
delete(id);
LOG.warn("Deleted unrestorable file for session {}", id);
}
catch (Exception x)
{
LOG.warn("Unable to delete unrestorable file {} for session {}", filename, id, x);
}
}
exception.set(e);
}
@ -255,6 +331,7 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
}
};
//ensure this runs with the context classloader set
_context.run(r);
@ -275,19 +352,20 @@ public class FileSessionDataStore extends AbstractSessionDataStore
File file = null;
if (_storeDir != null)
{
//remove any existing files for the session
deleteAllFiles(_storeDir, getIdWithContext(id));
delete(id);
//make a fresh file using the latest session expiry
file = new File(_storeDir, getIdWithContextAndExpiry(data));
String filename = getIdWithContextAndExpiry(data);
String idWithContext = getIdWithContext(id);
file = new File(_storeDir, filename);
try(FileOutputStream fos = new FileOutputStream(file,false))
{
save(fos, id, data);
_sessionFileMap.put(idWithContext, filename);
}
catch (Exception e)
{
e.printStackTrace();
if (file != null)
file.delete(); // No point keeping the file if we didn't save the whole session
throw new UnwriteableSessionDataException(id, _context,e);
@ -296,16 +374,100 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
/**
* Read the names of the existing session files and build a map of
* fully qualified session ids (ie with context) to filename. If there
* is more than one file for the same session, only the most recently modified will
* be kept and the rest deleted. At the same time, any files - for any context -
* that expired a long time ago will be cleaned up.
*
* @throws IllegalStateException if storeDir doesn't exist, isn't readable/writeable
* or contains 2 files with the same lastmodify time for the same session. Throws IOException
* if the lastmodifytimes can't be read.
*/
public void initializeStore ()
throws Exception
{
if (_storeDir == null)
throw new IllegalStateException("No file store specified");
if (!_storeDir.exists())
_storeDir.mkdirs();
else
{
if (!(_storeDir.isDirectory() &&_storeDir.canWrite() && _storeDir.canRead()))
throw new IllegalStateException(_storeDir.getAbsolutePath()+" must be readable/writeable dir");
//iterate over files in _storeDir and build map of session id to filename.
//if we come across files for sessions in other contexts, check if they're
//ancient and remove if necessary.
MultiException me = new MultiException();
long now = System.currentTimeMillis();
Files.walk(_storeDir.toPath(), 1, FileVisitOption.FOLLOW_LINKS)
.filter(p->!Files.isDirectory(p)).filter(p->isSessionFilename(p.getFileName().toString()))
.forEach(p->{
//first get rid of all ancient files, regardless of which
//context they are for
try
{
sweepFile(now, p);
}
catch (Exception x)
{
me.add(x);
}
String filename = p.getFileName().toString();
String context = getContextFromFilename(filename);
//now process it if it wasn't deleted, and it is for our context
if (Files.exists(p) && _contextString.equals(context))
{
//the session is for our context, populate the map with it
String sessionIdWithContext = getIdWithContextFromFilename(filename);
if (sessionIdWithContext != null)
{
//handle multiple session files existing for the same session: remove all
//but the file with the most recent expiry time
String existing = _sessionFileMap.putIfAbsent(sessionIdWithContext, filename);
if (existing != null)
{
//if there was a prior filename, work out which has the most
//recent modify time
try
{
long existingExpiry = getExpiryFromFilename(existing);
long thisExpiry = getExpiryFromFilename(filename);
if (thisExpiry > existingExpiry)
{
//replace with more recent file
Path existingPath = _storeDir.toPath().resolve(existing);
//update the file we're keeping
_sessionFileMap.put(sessionIdWithContext, filename);
//delete the old file
Files.delete(existingPath);
if (LOG.isDebugEnabled()) LOG.debug("Replaced {} with {}", existing, filename);
}
else
{
//we found an older file, delete it
Files.delete(p);
if (LOG.isDebugEnabled()) LOG.debug("Deleted expired session file {}", filename);
}
}
catch (IOException e)
{
me.add(e);
}
}
}
}
});
me.ifExceptionThrow();
}
}
/**
* @see org.eclipse.jetty.server.session.SessionDataStore#isPassivating()
@ -326,12 +488,14 @@ public class FileSessionDataStore extends AbstractSessionDataStore
@Override
public boolean exists(String id) throws Exception
{
File sessionFile = deleteOldFiles(_storeDir, getIdWithContext(id));
if (sessionFile == null || !sessionFile.exists())
String idWithContext = getIdWithContext(id);
String filename = _sessionFileMap.get(idWithContext);
if (filename == null)
return false;
//check the expiry
long expiry = getExpiryFromFile(sessionFile);
long expiry = getExpiryFromFilename(filename);
if (expiry <= 0)
return true; //never expires
else
@ -369,6 +533,7 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
}
/**
* Get the session id with its context.
*
@ -377,7 +542,7 @@ public class FileSessionDataStore extends AbstractSessionDataStore
*/
private String getIdWithContext (String id)
{
return _context.getCanonicalContextPath()+"_"+_context.getVhost()+"_"+id;
return _contextString+"_"+id;
}
/**
@ -391,35 +556,16 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
/**
* Work out which session id the file relates to.
* @param file the file to check
* @return the session id the file relates to.
*/
private String getIdFromFile (File file)
private String getIdFromFilename (String filename)
{
if (file == null)
if (filename == null)
return null;
String name = file.getName();
return name.substring(name.lastIndexOf('_')+1);
}
/**
* Get the expiry time of the session stored in the file.
* @param file the file from which to extract the expiry time
* @return the expiry time
*/
private long getExpiryFromFile (File file)
{
if (file == null)
return 0;
return getExpiryFromString(file.getName());
return filename.substring(filename.lastIndexOf('_')+1);
}
private long getExpiryFromString (String filename)
private long getExpiryFromFilename (String filename)
{
if (StringUtil.isBlank(filename) || filename.indexOf("_") < 0)
throw new IllegalStateException ("Invalid or missing filename");
@ -428,26 +574,24 @@ public class FileSessionDataStore extends AbstractSessionDataStore
return (s==null?0:Long.parseLong(s));
}
/**
* Extract the session id and context from the filename.
* @param file the file whose name to use
* @return the session id plus context
*/
private String getIdWithContextFromFile (File file)
private String getContextFromFilename (String filename)
{
if (file == null)
if (StringUtil.isBlank(filename))
return null;
String s = getIdWithContextFromString(file.getName());
return s;
int start = filename.indexOf('_');
int end = filename.lastIndexOf('_');
return filename.substring(start+1, end);
}
/**
* Extract the session id and context from the filename
* @param filename the name of the file to use
* @return the session id plus context
*/
private String getIdWithContextFromString (String filename)
private String getIdWithContextFromFilename (String filename)
{
if (StringUtil.isBlank(filename) || filename.indexOf('_') < 0)
return null;
@ -456,11 +600,31 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
/**
* Check if the filename matches our session pattern
* @param filename
* @return
* Check if the filename is a session filename.
*
* @param filename the filename to check
* @return true if the filename has the correct filename format
*/
private boolean match (String filename)
private boolean isSessionFilename (String filename)
{
if (StringUtil.isBlank(filename))
return false;
String[] parts = filename.split("_");
//Need at least 4 parts for a valid filename
if (parts.length < 4)
return false;
return true;
}
/**
* Check if the filename matches our session pattern
* and is a session for our context.
*
* @param filename the filename to check
* @return true if the filename has the correct filename format and is for this context
*/
private boolean isOurContextSessionFilename (String filename)
{
if (StringUtil.isBlank(filename))
return false;
@ -470,166 +634,11 @@ public class FileSessionDataStore extends AbstractSessionDataStore
if (parts.length < 4)
return false;
return true;
}
/**
* Find a File for the session id for the current context.
*
* @param storeDir the session storage directory
* @param id the session id
* @return the file
*/
private File getFile (final File storeDir, final String id)
{
File[] files = storeDir.listFiles (new FilenameFilter() {
/**
* @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
*/
@Override
public boolean accept(File dir, String name)
{
if (dir != storeDir)
//Also needs to be for our context
String context = getContextFromFilename(filename);
if (context == null)
return false;
return (name.contains(getIdWithContext(id)));
}
});
if (files == null || files.length < 1)
return null;
return files[0];
}
/**
* Remove all existing session files for the session in the context
* @param storeDir where the session files are stored
* @param idInContext the session id within a particular context
*/
private void deleteAllFiles(final File storeDir, final String idInContext)
{
File[] files = storeDir.listFiles (new FilenameFilter() {
/**
* @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
*/
@Override
public boolean accept(File dir, String name)
{
if (dir != storeDir)
return false;
return (name.contains(idInContext));
}
});
//no files for that id
if (files == null || files.length < 1)
return;
//delete all files
for (File f:files)
{
try
{
Files.deleteIfExists(f.toPath());
}
catch (Exception e)
{
LOG.warn("Unable to delete session file", e);
}
}
}
/**
* Delete all but the most recent file for a given session id in a context.
*
* @param storeDir the directory in which sessions are stored
* @param idWithContext the id of the session
* @return the most recent remaining file for the session, can be null
*/
private File deleteOldFiles (final File storeDir, final String idWithContext)
{
File[] files = storeDir.listFiles (new FilenameFilter() {
/**
* @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
*/
@Override
public boolean accept(File dir, String name)
{
if (dir != storeDir)
return false;
if (!match(name))
return false;
return (name.contains(idWithContext));
}
});
//no file for that session
if (files == null || files.length == 0)
return null;
//delete all but the most recent file
File newest = null;
for (File f:files)
{
try
{
if (newest == null)
{
//haven't looked at any files yet
newest = f;
}
else
{
if (f.lastModified() > newest.lastModified())
{
//this file is more recent
Files.deleteIfExists(newest.toPath());
newest = f;
}
else if (f.lastModified() < newest.lastModified())
{
//this file is older
Files.deleteIfExists(f.toPath());
}
else
{
//files have same last modified times, decide based on latest expiry time
long exp1 = getExpiryFromFile(newest);
long exp2 = getExpiryFromFile(f);
if (exp2 >= exp1)
{
//this file has a later expiry date
Files.deleteIfExists(newest.toPath());
newest = f;
}
else
{
//this file has an earlier expiry date
Files.deleteIfExists(f.toPath());
}
}
}
}
catch (Exception e)
{
LOG.warn("Unable to delete old session file", e);
}
}
return newest;
return (_contextString.equals(context));
}

View File

@ -18,6 +18,9 @@
package org.eclipse.jetty.server.session;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Collections;
@ -37,6 +40,7 @@ import org.junit.Test;
public class FileSessionManagerTest
{
public static final long ONE_DAY = (1000L*60L*60L*24L);
private static StdErrLog _log;
private static boolean _stacks;
@ -110,6 +114,12 @@ public class FileSessionManagerTest
/**
* When starting the filestore, check that really old expired
* files are deleted irrespective of context session belongs to.
*
* @throws Exception
*/
@Test
public void testDeleteOfOlderFiles() throws Exception
{
@ -129,42 +139,139 @@ public class FileSessionManagerTest
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
FS.ensureEmpty(testDir);
ds.setStoreDir(testDir);
handler.setSessionIdManager(idmgr);
handler.start();
//create a bunch of older files for same session abc
//create a really old file for session abc
String name1 = "100__0.0.0.0_abc";
File f1 = new File(testDir, name1);
if (f1.exists())
Assert.assertTrue(f1.delete());
f1.createNewFile();
//create another really old file for session abc
Thread.sleep(1100);
String name2 = "101__0.0.0.0_abc";
File f2 = new File(testDir, name2);
if (f2.exists())
Assert.assertTrue(f2.delete());
f2.createNewFile();
//make one file for session abc that should not have expired
Thread.sleep(1100);
String name3 = "102__0.0.0.0_abc";
long exp = System.currentTimeMillis() + ONE_DAY;
String name3 = Long.toString(exp)+"__0.0.0.0_abc";
File f3 = new File(testDir, name3);
if (f3.exists())
Assert.assertTrue(f3.delete());
f3.createNewFile();
//make a file that is for a different context
//that expired a long time ago - should be
//removed by sweep on startup
Thread.sleep(1100);
String name4 = "1099_foo_0.0.0.0_abc";
File f4 = new File(testDir, name4);
if (f4.exists())
Assert.assertTrue(f4.delete());
f4.createNewFile();
//make a file that is for a different context
//that should not have expired - ensure it is
//not removed
exp = System.currentTimeMillis() + ONE_DAY;
String name5 = Long.toString(exp)+"_foo_0.0.0.0_abcdefg";
File f5 = new File(testDir, name5);
if (f5.exists())
Assert.assertTrue(f5.delete());
f5.createNewFile();
//make a file that is for a different context
//that expired, but only recently - it should
//not be removed by the startup process
exp = System.currentTimeMillis() - 1000L;
String name6 = Long.toString(exp)+"_foo_0.0.0.0_abcdefg";
File f6 = new File(testDir, name5);
if (f6.exists())
Assert.assertTrue(f6.delete());
f6.createNewFile();
handler.setSessionIdManager(idmgr);
handler.start();
Session session = handler.getSession("abc");
Assert.assertTrue(!f1.exists());
Assert.assertTrue(!f2.exists());
Assert.assertTrue(f3.exists());
Assert.assertTrue(!f4.exists());
Assert.assertTrue(f5.exists());
Assert.assertTrue(f6.exists());
}
/**
* Tests that only the most recent file will be
* loaded into the cache, even if it is already
* expired. Other recently expired files for
* same session should be deleted.
* @throws Exception
*/
@Test
public void testLoadOnlyMostRecent() throws Exception
{
Server server = new Server();
SessionHandler handler = new SessionHandler();
handler.setServer(server);
final DefaultSessionIdManager idmgr = new DefaultSessionIdManager(server);
idmgr.setServer(server);
server.setSessionIdManager(idmgr);
FileSessionDataStore ds = new FileSessionDataStore();
ds.setGracePeriodSec(100); //set graceperiod to 100sec to control what we consider as very old
ds.setDeleteUnrestorableFiles(false); //turn off deletion of unreadable session files
DefaultSessionCache ss = new DefaultSessionCache(handler);
handler.setSessionCache(ss);
ss.setSessionDataStore(ds);
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
FS.ensureEmpty(testDir);
ds.setStoreDir(testDir);
long now = System.currentTimeMillis();
//create a file for session abc that expired 5sec ago
long exp = now -5000L;
String name1 = Long.toString(exp)+"__0.0.0.0_abc";
File f1 = new File(testDir, name1);
if (f1.exists())
Assert.assertTrue(f1.delete());
f1.createNewFile();
//create a file for same session that expired 4 sec ago
exp = now - 4000L;
String name2 = Long.toString(exp)+"__0.0.0.0_abc";
File f2 = new File(testDir, name2);
if (f2.exists())
Assert.assertTrue(f2.delete());
f2.createNewFile();
//make a file for same session that expired 3 sec ago
exp = now - 3000L;
String name3 = Long.toString(exp)+"__0.0.0.0_abc";
File f3 = new File(testDir, name3);
if (f3.exists())
Assert.assertTrue(f3.delete());
f3.createNewFile();
handler.setSessionIdManager(idmgr);
handler.start();
Assert.assertFalse(f1.exists());
Assert.assertFalse(f2.exists());
Assert.assertTrue(f3.exists());
}
@Test
public void testUnrestorableFileRemoval() throws Exception
@ -176,25 +283,22 @@ public class FileSessionManagerTest
idmgr.setServer(server);
server.setSessionIdManager(idmgr);
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
FS.ensureEmpty(testDir);
String expectedFilename = (System.currentTimeMillis()+ 10000)+"__0.0.0.0_validFile123";
Assert.assertTrue(new File(testDir, expectedFilename).createNewFile());
Assert.assertTrue("File should exist!", new File(testDir, expectedFilename).exists());
DefaultSessionCache ss = new DefaultSessionCache(handler);
FileSessionDataStore ds = new FileSessionDataStore();
ss.setSessionDataStore(ds);
handler.setSessionCache(ss);
ds.setDeleteUnrestorableFiles(true); //invalid file will be removed
handler.setSessionIdManager(idmgr);
File testDir = MavenTestingUtils.getTargetTestingDir("hashes");
FS.ensureEmpty(testDir);
ds.setStoreDir(testDir);
handler.start();
String expectedFilename = (System.currentTimeMillis()+ 10000)+"__0.0.0.0_validFile123";
Assert.assertTrue(new File(testDir, expectedFilename).createNewFile());
Assert.assertTrue("File should exist!", new File(testDir, expectedFilename).exists());
Session session = handler.getSession("validFile123");
Assert.assertTrue("File shouldn't exist!", !new File(testDir,expectedFilename).exists());
@ -315,6 +419,6 @@ public class FileSessionManagerTest
{
nonNumber.delete();
}
}
}
}

View File

@ -24,6 +24,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.eclipse.jetty.util.IO;
@ -87,12 +89,13 @@ public class FileTestHelper
break;
}
}
if (fname != null)
return new File (_tmpDir, fname);
return null;
}
public static void assertFileExists (String sessionId, boolean exists)
public static void assertSessionExists (String sessionId, boolean exists)
{
assertNotNull(_tmpDir);
assertTrue(_tmpDir.exists());
@ -115,6 +118,29 @@ public class FileTestHelper
assertFalse(found);
}
public static void assertFileExists (String filename, boolean exists)
{
assertNotNull(_tmpDir);
assertTrue(_tmpDir.exists());
File file = new File (_tmpDir, filename);
if (exists)
assertTrue(file.exists());
else
assertFalse(file.exists());
}
public static void createFile (String filename)
throws IOException
{
assertNotNull(_tmpDir);
assertTrue(_tmpDir.exists());
File file = new File (_tmpDir, filename);
Files.deleteIfExists(file.toPath());
file.createNewFile();
}
public static void deleteFile (String sessionId)
{

View File

@ -47,7 +47,7 @@ public class NonClusteredSessionScavengingTest extends AbstractNonClusteredSessi
@Override
public void assertSession(String id, boolean exists)
{
FileTestHelper.assertFileExists(id, exists);
FileTestHelper.assertSessionExists(id, exists);
}

View File

@ -20,10 +20,12 @@
package org.eclipse.jetty.server.session;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@ -67,10 +69,90 @@ public class TestFileSessions extends AbstractTestBase
return FileTestHelper.newSessionDataStoreFactory();
}
@Test
public void testSweep () throws Exception
{
int scavengePeriod = 2;
String contextPath = "/test";
String servletMapping = "/server";
int inactivePeriod = 5;
int gracePeriod = 10;
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
FileSessionDataStoreFactory storeFactory = (FileSessionDataStoreFactory)createSessionDataStoreFactory();
storeFactory.setGracePeriodSec(gracePeriod);
TestServer server1 = new TestServer(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory);
server1.addContext(contextPath).addServlet(TestServlet.class, servletMapping);
try
{
server1.start();
//create file not for our context that expired long ago and should be removed by sweep
FileTestHelper.createFile("101_foobar_0.0.0.0_sessiona");
FileTestHelper.assertSessionExists("sessiona", true);
//create a file not for our context that is not expired and should be ignored
String nonExpiredForeign = (System.currentTimeMillis()+TimeUnit.DAYS.toMillis(1))+"_foobar_0.0.0.0_sessionb";
FileTestHelper.createFile(nonExpiredForeign);
FileTestHelper.assertFileExists(nonExpiredForeign, true);
//create a file not for our context that is recently expired, a thus ignored by sweep
String expiredForeign = (System.currentTimeMillis()-TimeUnit.SECONDS.toMillis(1))+"_foobar_0.0.0.0_sessionc";
FileTestHelper.createFile(expiredForeign);
FileTestHelper.assertFileExists(expiredForeign, true);
//create a file that is not a session file, it should be ignored
FileTestHelper.createFile("whatever.txt");
FileTestHelper.assertFileExists("whatever.txt", true);
//create a file that is a non-expired session file for our context that should be ignored
String nonExpired = (System.currentTimeMillis()+TimeUnit.DAYS.toMillis(1))+"_test_0.0.0.0_sessionb";
FileTestHelper.createFile(nonExpired);
FileTestHelper.assertFileExists(nonExpired, true);
//create a file that is a never-expire session file for our context that should be ignored
String neverExpired = "0_test_0.0.0.0_sessionc";
FileTestHelper.createFile(neverExpired);
FileTestHelper.assertFileExists(neverExpired, true);
//create a file that is a never-expire session file for another context that should be ignored
String foreignNeverExpired = "0_test_0.0.0.0_sessionc";
FileTestHelper.createFile(foreignNeverExpired);
FileTestHelper.assertFileExists(foreignNeverExpired, true);
//need to wait to ensure scavenge runs so sweeper runs
Thread.currentThread().sleep(2000L*scavengePeriod);
FileTestHelper.assertSessionExists("sessiona", false);
FileTestHelper.assertFileExists("whatever.txt", true);
FileTestHelper.assertFileExists(nonExpired, true);
FileTestHelper.assertFileExists(nonExpiredForeign, true);
FileTestHelper.assertFileExists(expiredForeign, true);
FileTestHelper.assertFileExists(neverExpired, true);
FileTestHelper.assertFileExists(foreignNeverExpired, true);
}
finally
{
server1.stop();
}
}
@Test
public void test () throws Exception
{
String contextPath = "";
String contextPath = "/test";
String servletMapping = "/server";
int inactivePeriod = 5;
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
@ -97,26 +179,37 @@ public class TestFileSessions extends AbstractTestBase
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
//check that the file for the session exists after creating the session
FileTestHelper.assertFileExists(TestServer.extractSessionId(sessionCookie), true);
FileTestHelper.assertSessionExists(TestServer.extractSessionId(sessionCookie), true);
File file1 = FileTestHelper.getFile(TestServer.extractSessionId(sessionCookie));
//request the session and check that the file for the session exists with an updated lastmodify
//request the session and check that the file for the session was changed
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=check");
request.header("Cookie", sessionCookie);
ContentResponse response2 = request.send();
assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
FileTestHelper.assertFileExists(TestServer.extractSessionId(sessionCookie), true);
FileTestHelper.assertSessionExists(TestServer.extractSessionId(sessionCookie), true);
File file2 = FileTestHelper.getFile(TestServer.extractSessionId(sessionCookie));
assertTrue (!file1.equals(file2));
assertTrue (file2.lastModified() > file1.lastModified());
assertFalse (file1.exists());
assertTrue(file2.exists());
//check expiry time in filename changed
String tmp = file1.getName();
tmp = tmp.substring(0, tmp.indexOf("_"));
long f1 = Long.valueOf(tmp);
tmp = file2.getName();
tmp = tmp.substring(0, tmp.indexOf("_"));
long f2 = Long.valueOf(tmp);
assertTrue (f2>f1);
//invalidate the session and verify that the session file is deleted
request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=remove");
request.header("Cookie", sessionCookie);
response2 = request.send();
assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
FileTestHelper.assertFileExists(TestServer.extractSessionId(sessionCookie), false);
FileTestHelper.assertSessionExists(TestServer.extractSessionId(sessionCookie), false);
//make another session
response1 = client.GET("http://localhost:" + port1 + contextPath + servletMapping + "?action=init");
@ -124,11 +217,11 @@ public class TestFileSessions extends AbstractTestBase
sessionCookie = response1.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
FileTestHelper.assertFileExists(TestServer.extractSessionId(sessionCookie), true);
FileTestHelper.assertSessionExists(TestServer.extractSessionId(sessionCookie), true);
//wait for it to be scavenged
Thread.currentThread().sleep((inactivePeriod + 2)*1000);
FileTestHelper.assertFileExists(TestServer.extractSessionId(sessionCookie), false);
FileTestHelper.assertSessionExists(TestServer.extractSessionId(sessionCookie), false);
}
finally
@ -157,11 +250,12 @@ public class TestFileSessions extends AbstractTestBase
{
HttpSession session = request.getSession(false);
session.invalidate();
//assertTrue(session == null);
}
else if ("check".equals(action))
{
HttpSession session = request.getSession(false);
assertTrue(session != null);
try {Thread.currentThread().sleep(1);}catch (Exception e) {e.printStackTrace();}
}
}
}