On Friday 07 December 2001 13:12, Chuck Esterbrook wrote: > On Friday 07 December 2001 11:08 am, Geoffrey Talvola wrote: > > I'm taking a closer look at the implementation of > > SessionDynamicStore. It looks like it actually checks the > > filesystem on _every_ request, even if the session is in memory. > > This would seem to slow it down considerably. > > > > That plus the concurrency issue and it looks like > > SessionDynamicStore could stand a rewrite. > > It's other deficiency that I recall is that it's hard coded to > FileStore. If someone had a SQL store, or something else, they > might want dynamic to use that instead.
Have a look at the DynamicSessionStore in the file I've attached. It's just been rewired to handle Chuck's proposal. It's based on the experimental code, but most of it should transfer across easily
#!/usr/bin/env python # $Id$ """Provides a SessionStore for WebKit, that manages an Application's user sessions. """ __author__ = 'The Webware Development Team' __revision__ = "$Revision$"[11:-2] ################################################## ## DEPENDENCIES ## import os import os.path from glob import glob import sys from UserDict import UserDict from threading import Thread, Event # used to create a worker threads that manages sessions from string import join as strJoin, lower as strLower from time import time as currentTime, localtime, sleep try: from cPickle import load, dump except ImportError: from pickle import load, dump # intra-package imports ... from Webware.Utils.SettingsManager import SettingsManager ################################################## ## GLOBALS & CONSTANTS ## True = (1==1) False = (1==0) ################################################## ## CLASSES ## class Error(Exception): pass class SessionStore(UserDict, SettingsManager): """A class that manages an Application's user sessions from memory.""" def __init__(self, app, restoreFiles=True): UserDict.__init__(self) self.initializeSettings() self.setSetting('sessionSweepInterval', app.setting('sessionSweepInterval') ) self._appID = app.ID() self._running = False self._checkEvent = Event() def initializeSettings(self): """Initialize the default settings.""" self._settings = { 'sessionSweepInterval':2, # seconds } def start(self): if not self._running: self._running = True self._managerThread = Thread(target=self.intervalSweep) self._managerThread.start() def __getitem__(self, ID): sess = self.data[ID] sess.awake() return sess def storeSession(self, sess): if sess._expired: try: del self[sess.ID()] except: pass else: self[sess.ID()] = sess def clear(self): self.data.clear() def shutdown(self): if self._running: self._running = False self._checkEvent.set() try: self._managerThread.join() except: pass def intervalSweep(self): ## init shortcut name bindings checkInterval = self.setting('sessionSweepInterval') checkEvent = self._checkEvent sessions = self.data.items cleanStaleSessions = self.cleanStaleSessions while 1: if not self._running: return cleanStaleSessions() checkEvent.wait(checkInterval) def cleanStaleSessions(self): pass def encoder(self): return self._encoder def decoder(self): return self._decoder def setEncoderDecoder(self, encoder, decoder): self._encoder = encoder self._decoder = decoder class SessionFileStore(SessionStore): """A class that manages an Application's user sessions from file""" def __init__(self, application): SessionStore.__init__(self, application) self.setEncoderDecoder(dump, load) self._sessionDir = application.setting('sessionsTmpDir', 'Sessions') self._timeoutPeriod = application.setting('sessionTimeout',60)*60#seconds if not os.path.exists(self._sessionDir) and not os.path.isdir(self._sessionDir): os.mkdir(self._sessionDir) self._sessFileNameBase = os.path.join( self._sessionDir, self._appID.replace('/','_').replace('\\','_') ) def __getitem__(self, ID): filename = self.filenameForID(ID) try: file = open(filename) except IOError: raise KeyError, ID sess = self.decoder()(file) file.close() sess.awake() return sess def __setitem__(self, ID, item): filename = self.filenameForID(ID) tmp = os.tempnam(os.path.dirname(filename), 'tmp') file = open(tmp, 'w') self.encoder()(item, file) file.close() os.rename(tmp, filename) def __delitem__(self, ID, pathExists=os.path.exists): filename = self.filenameForID(ID) if not pathExists(filename): raise KeyError, ID os.remove(filename) def has_key(self, ID, pathExists=os.path.exists): return pathExists(self.filenameForID(ID)) def keys(self): start = len(self._sessFileNameBase)+1 end = -len('.ses') keys = glob(self._sessFileNameBase + '_*.ses') keys = map(lambda key, start=start, end=end: key[start:end], keys) return keys def __len__(self): return len(self.keys()) def filenameForID(self, ID): return self._sessFileNameBase + '_%s.ses' % ID def clear(self): for filename in glob(self._sessFileNameBase + '*.ses'): os.remove(filename) def cleanStaleSessions(self, currentTime=currentTime, getmtime = os.path.getmtime): timeoutPeriod = self._timeoutPeriod currTime = currentTime() for filename in glob(self._sessFileNameBase + '*.ses'): if (getmtime (filename) + timeoutPeriod) < currTime: file = open(filename) sess = self.decoder()(file) file.close() sess.expire() os.remove(filename) class SessionMemoryStore(SessionStore): """A class that manages an Application's user sessions from memory.""" def __init__(self, application, restoreFiles=True): SessionStore.__init__(self, application) self._filestore = filestore = SessionFileStore(application) if restoreFiles == 1: keys = filestore.keys() for i in keys: self[i] = filestore[i] def storeSessionsToDisk(self): for i in self.keys(): self._filestore[i]=self[i] def shutdown(self): self.storeSessionsToDisk() SessionStore.shutdown(self) def intervalSweep(self): ## init shortcut name bindings checkInterval = self.setting('sessionSweepInterval') checkEvent = self._checkEvent sessions = self.data.items cleanStaleSessions = self.cleanStaleSessions while 1: if not self._running: return cleanStaleSessions() checkEvent.wait(checkInterval) def cleanStaleSessions(self): if self.data: currTime = currentTime() for ID, session in self.data.items(): if session.expiryTime() < currTime: session.expire() del self[ID] class SessionDynamicStore(SessionStore): """ Stores the session in Memory and Files (or some other secondary store). This can be used either in a persistent app server or a cgi framework. To use this Session Store, set SessionStore in Application.config to 'Dynamic'. Other variables which can be set in Application.config are: 'MaxDynamicMemorySessions', which sets the maximum number of sessions that can be in the Memory SessionStore at one time. Default is 10,000. 'DynamicSessionTimeout', which sets the default time for a session to stay in memory with no activity. Default is 15 minutes. When specifying this in Application.config, use minutes. """ def __init__(self, app, restoreFiles=True, secondaryStoreClass=SessionFileStore): SessionStore.__init__(self, app) self._2ndStore = secondaryStoreClass(app) self._memoryStore = SessionMemoryStore(app, restoreFiles=0) # specifies after what period of time a session is automatically moved to file self.moveTo2ndStoreInterval = app.setting('dynamicSessionTimeout', 15) * 60 #maxDynamicMemorySessions is what the user actually sets in Application.config self._maxDynamicMemorySessions = app.setting('maxDynamicMemorySessions', 10000) self._2ndStoreSweepCount = 0 def __len__(self): return len(self._memoryStore) + len(self._2ndStore) def __getitem__(self, key): if self._memoryStore.has_key(key): pass elif self._2ndStore.has_key(key): self.moveToMemory(key) # let it raise a KeyError otherwise return self._memoryStore[key] def __setitem__(self, key, item): self._memoryStore[key] = item try: del self._2ndStore[key] except KeyError: pass def __delitem__(self, key): if self._memoryStore.has_key(key): del self._memoryStore[key] if self._2ndStore.has_key(key): del self._2ndStore[key] def has_key(self, key): return self._memoryStore.has_key(key) or self._2ndStore.has_key(key) def keys(self): return self._memoryStore.keys() + self._2ndStore.keys() def clear(self): self._memoryStore.clear() self._2ndStore.clear() def moveToMemory(self, key): self._memoryStore[key] = self._2ndStore[key] del self._2ndStore[key] def moveTo2ndStore(self, key): self._2ndStore[key] = self._memoryStore[key] del self._memoryStore[key] def shutdown(self): SessionStore.shutdown(self) self.storeSessionsTo2ndStore() def storeSessionsTo2ndStore(self): for i in self._memoryStore.keys(): self.moveTo2ndStore(i) # It's OK for a session to moved from memory to _2ndStore or vice versa in # between the time we get the keys and the time we actually ask for the # session's access time. It may take a while for the _2ndStore sweep to get # completed. def cleanStaleSessions(self): try: if self._2ndStoreSweepCount == 0: self._2ndStore.cleanStaleSessions() self._memoryStore.cleanStaleSessions() except KeyError: pass if self._2ndStoreSweepCount < 4: self._2ndStoreSweepCount += 1 else: self._2ndStoreSweepCount = 0 now = currentTime() delta = now - self.moveTo2ndStoreInterval for i in self._memoryStore.keys(): if self._memoryStore[i].lastAccessTime() < delta: self.moveTo2ndStore(i) if len(self._memoryStore) > self._maxDynamicMemorySessions: keys = self._memoryStore.keys() keys.sort(self.sortFunc) excess = len(self._memoryStore) - self._maxDynamicMemorySessions for i in keys[:-excess]: self.moveTo2ndStore(i) def sortFunc(self,x,y): if self._memoryStore[x].lastAccessTime() > self._memoryStore[y].lastAccessTime(): return -1 else: return 1 try: from ZEO import ClientStorage from ZODB import DB class SessionZODBStore(SessionStore): """A class that manages an Application's user sessions from the ZODB. @@TR: This isn't implemented yet!!! """ def __init__(self, application, restoreFiles=True): SessionStore.__init__(self, application) self._filestore = filestore = SessionFileStore(application) if restoreFiles == 1: keys = filestore.keys() for i in keys: self[i] = filestore[i] def storeAllSessions(self): for i in self.keys(): self._filestore[i]=self[i] def shutdown(self): SessionStore.shutdown(self) self.storeAllSessions() def intervalSweep(self): ## init shortcut name bindings checkInterval = self.setting('sessionSweepInterval') checkEvent = self._checkEvent sessions = self.data.items cleanStaleSessions = self.cleanStaleSessions while 1: if not self._running: return cleanStaleSessions() checkEvent.wait(checkInterval) def cleanStaleSessions(self): if self.data: currTime = currentTime() for ID, session in self.data.items(): if session.expiryTime() < currTime: session.expire() del self[ID] ## end class def # make an alias ZODB = SessionZODBStore except: ## make a dummy class instead class SessionZODBStore: pass # make an alias ZODB = SessionZODBStore ## create an aliases File = SessionFileStore Memory = SessionMemoryStore Dynamic = SessionDynamicStore