From 22c49a959085fe8b3df5a2ef983754972beb0740 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Thu, 30 Oct 2008 14:43:25 -0700 Subject: [PATCH] Rewrote Session_storage class, because it was periodically raising ProgrammingErrors due to simultaneous cursor access from different threads. --- controller/Session_storage.py | 88 +++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/controller/Session_storage.py b/controller/Session_storage.py index 09dfe0c..f091cad 100644 --- a/controller/Session_storage.py +++ b/controller/Session_storage.py @@ -1,29 +1,79 @@ import cherrypy -from psycopg2 import ProgrammingError -from cherrypy.filters.sessionfilter import PostgreSQLStorage +import cPickle as pickle +from psycopg2 import ProgrammingError -class Session_storage( PostgreSQLStorage ): - """ - A wrapper for CherryPy's PostgreSQLStorage class that commits the current transaction to the - database so session changes actually take effect. +class Session_storage( object ): """ + A CherryPy session storage class, originally based on CherryPy's PostgreSQLStorage. It assumes + a table like this: + create table session ( + id text, + data text, + expiration_time timestamp + ) + + It differs from PostgreSQLStorage in the following ways: + + * changes to the database are actually committed after they are made + * a new cursor is created for each database access to prevent problems with multiple threads + * a connection is requested from cherrypy.root.database instead of a session_filter.get_db method + * __del__ is not implemented because it should not be relied upon + * no locking is implemented + """ def __init__( self ): - self.db = cherrypy.root.database.get_connection() - self.cursor = self.db.cursor() + self.conn = cherrypy.root.database.get_connection() + + def load( self, id ): + cursor = self.conn.cursor() - def load( self, *args, **kwargs ): - try: - return PostgreSQLStorage.load( self, *args, **kwargs ) - # catch "ProgrammingError: no results to fetch" from self.cursor.fetchall() - except ProgrammingError: + # Select session data from table + cursor.execute( + 'select data, expiration_time from session where id=%s', + (id,)) + rows = cursor.fetchall() + if not rows: return None + pickled_data, expiration_time = rows[0] + # Unpickle data + data = pickle.loads(pickled_data) + return (data, expiration_time) + + def save( self, id, data, expiration_time ): + cursor = self.conn.cursor() - def save( self, *args, **kwargs ): - PostgreSQLStorage.save( self, *args, **kwargs ) - self.db.commit() + # Try to delete session if it was already there + cursor.execute( + 'delete from session where id=%s', + (id,)) + # Pickle data + pickled_data = pickle.dumps(data) + # Insert new session data + cursor.execute( + 'insert into session (id, data, expiration_time) values (%s, %s, %s)', + (id, pickled_data, expiration_time)) - def clean_up( self, *args, **kwargs ): - PostgreSQLStorage.clean_up( self, *args, **kwargs ) - self.db.commit() + self.conn.commit() + + def clean_up( self, sess ): + cursor = self.conn.cursor() + + now = datetime.datetime.now() + cursor.execute( + 'select data from session where expiration_time < %s', + (now,)) + rows = cursor.fetchall() + for row in rows: + sess.on_delete_session(row[0]) + cursor.execute( + 'delete from session where expiration_time < %s', + (now,)) + + self.conn.commit() + + def acquire_lock( self ): + raise NotImplemented() + + def release_lock( self ): + raise NotImplemented()