Ticket #807 (enhancement)
Opened 3 weeks ago
FileSession split by directory (PATCH INCLUDED)
Status: new
| Reported by: | theatrus@gmail.com | Assigned to: | no_mind |
|---|---|---|---|
| Priority: | normal | Milestone: | 3.0 |
| Component: | sessions | Keywords: | |
| Cc: |
Busy CherryPy sites running with FileSession? can generate tens of thousands of user sessions in the FileSession? store. FileSession? stores all of these in a single directory, which is not optimal for some file systems.
This patch addresses this problem by splitting the sessions in up to 256 dynamically created directories based on their first byte of ID (aa, 23, etc).
This patch is against 3.0.3
---
cherrypy/lib/sessions.py | 55 +++++++++++++++++++++++++++++++---------------
1 files changed, 37 insertions(+), 18 deletions(-)
diff --git a/cherrypy/lib/sessions.py b/cherrypy/lib/sessions.py
index 4e1676e..15aea8e 100644
--- a/cherrypy/lib/sessions.py
+++ b/cherrypy/lib/sessions.py
@@ -259,8 +259,16 @@ class FileSession(Session):
% (len(lockfiles), plural,
os.path.abspath(self.storage_path)))
+ def _get_directory(self):
+ begin_id = self.id[0:2]
+ f = os.path.join(self.storage_path, begin_id)
+ if not os.path.normpath(f).startswith(self.storage_path):
+ raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
+ return f
+
def _get_file_path(self):
- f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
+ fdir = self._get_directory()
+ f = os.path.join(fdir, self.SESSION_PREFIX + self.id)
if not os.path.normpath(f).startswith(self.storage_path):
raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
return f
@@ -277,7 +285,16 @@ class FileSession(Session):
except (IOError, EOFError):
return None
+ def _mkdir(self):
+ d = self._get_directory()
+
+ try:
+ os.mkdir(d) # Create a nested directory for this entry
+ except (OSError):
+ pass
+
def _save(self, expiration_time):
+
f = open(self._get_file_path(), "wb")
try:
pickle.dump((self._data, expiration_time), f)
@@ -292,6 +309,7 @@ class FileSession(Session):
def acquire_lock(self, path=None):
if path is None:
+ self._mkdir()
path = self._get_file_path()
path += self.LOCK_SUFFIX
while True:
@@ -314,23 +332,24 @@ class FileSession(Session):
"""Clean up expired sessions."""
now = datetime.datetime.now()
# Iterate over all session files in self.storage_path
- for fname in os.listdir(self.storage_path):
- if (fname.startswith(self.SESSION_PREFIX)
- and not fname.endswith(self.LOCK_SUFFIX)):
- # We have a session file: lock and load it and check
- # if it's expired. If it fails, nevermind.
- path = os.path.join(self.storage_path, fname)
- self.acquire_lock(path)
- try:
- contents = self._load(path)
- # _load returns None on IOError
- if contents is not None:
- data, expiration_time = contents
- if expiration_time < now:
- # Session expired: deleting it
- os.unlink(path)
- finally:
- self.release_lock(path)
+ for dname in os.listdir(self.storage_path): #iterate over directories
+ for fname in os.listdir(os.path.join(self.storage_path, self.dname)):
+ if (fname.startswith(self.SESSION_PREFIX)
+ and not fname.endswith(self.LOCK_SUFFIX)):
+ # We have a session file: lock and load it and check
+ # if it's expired. If it fails, nevermind.
+ path = os.path.join(self.storage_path, fname)
+ self.acquire_lock(path)
+ try:
+ contents = self._load(path)
+ # _load returns None on IOError
+ if contents is not None:
+ data, expiration_time = contents
+ if expiration_time < now:
+ # Session expired: deleting it
+ os.unlink(path)
+ finally:
+ self.release_lock(path)
class PostgresqlSession(Session):

