Rewrote Session_storage class, because it was periodically raising ProgrammingErrors due to simultaneous cursor access from different threads.
This commit is contained in:
parent
3a5ca1d462
commit
22c49a9590
|
@ -1,29 +1,79 @@
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from psycopg2 import ProgrammingError
|
import cPickle as pickle
|
||||||
from cherrypy.filters.sessionfilter import PostgreSQLStorage
|
from psycopg2 import ProgrammingError
|
||||||
|
|
||||||
|
|
||||||
class Session_storage( PostgreSQLStorage ):
|
class Session_storage( object ):
|
||||||
"""
|
|
||||||
A wrapper for CherryPy's PostgreSQLStorage class that commits the current transaction to the
|
|
||||||
database so session changes actually take effect.
|
|
||||||
"""
|
"""
|
||||||
|
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 ):
|
def __init__( self ):
|
||||||
self.db = cherrypy.root.database.get_connection()
|
self.conn = cherrypy.root.database.get_connection()
|
||||||
self.cursor = self.db.cursor()
|
|
||||||
|
def load( self, id ):
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
|
||||||
def load( self, *args, **kwargs ):
|
# Select session data from table
|
||||||
try:
|
cursor.execute(
|
||||||
return PostgreSQLStorage.load( self, *args, **kwargs )
|
'select data, expiration_time from session where id=%s',
|
||||||
# catch "ProgrammingError: no results to fetch" from self.cursor.fetchall()
|
(id,))
|
||||||
except ProgrammingError:
|
rows = cursor.fetchall()
|
||||||
|
if not rows:
|
||||||
return None
|
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 ):
|
# Try to delete session if it was already there
|
||||||
PostgreSQLStorage.save( self, *args, **kwargs )
|
cursor.execute(
|
||||||
self.db.commit()
|
'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 ):
|
self.conn.commit()
|
||||||
PostgreSQLStorage.clean_up( self, *args, **kwargs )
|
|
||||||
self.db.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()
|
||||||
|
|
Reference in New Issue