Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Ticket #807 (enhancement)

Opened 4 months ago

Last modified 1 month ago

FileSession split by directory (PATCH INCLUDED)

Status: new

Reported by: theatrus@gmail.com Assigned to: no_mind
Priority: normal Milestone: 3.2
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):

Change History

05/25/08 17:25:37: Modified by fumanchu

  • milestone changed from 3.0 to 3.2.

07/09/08 02:34:56: Modified by nick125

Maybe this should be a separate Session handler altogether? Providing I understand this patch correctly, I'm not sure that changing the FileSession? behavior like this would be a good idea. Maybe something such as "DirectorySession"?

07/09/08 10:54:05: Modified by fumanchu

> Maybe this should be a separate Session handler altogether?

I tend to agree. Perhaps a subclass of FileHandler? if a good amount of code can be shared...

Hosted by WebFaction

Log in as guest/cpguest to create tickets