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