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

Reply via email to