I have deployed a website on Google-App-Engine with Python.
Since GAE does not guarantee "keep-alive", I have implemented a stateless server:
Upon every change in internal variables, they are immediately stored into GQL data-base.
Whenever the process is started, all internal variables are loaded from GQL data-base.
I have a scenario which rarely throws an exception, and I have not been able to track it down:
Client sends a synchronous AJAX POST request.
Server creates a session and sends a unique session-ID in the response.
Client sends a synchronous AJAX GET request with the session-ID as an argument.
Server sends some text message in the response.
Since the client requests are synchronous, the entire sequence is also synchronous.
Here is the relevant mapping within my server:
from webapp2 import WSGIApplication
from Handler import MyRequestHandler
app = WSGIApplication([
('/request1' ,MyRequestHandler), # post request
('/request2(.*)',MyRequestHandler), # get request
])
Here is the relevant request-handling within my server:
from webapp2 import RequestHandler
from Server import MyServer
myServer = MyServer()
class MyRequestHandler(RequestHandler):
def post(self):
try:
if self.request.path.startswith('/request1'):
sessionId = myServer.GetNewSessionId()
self.SendContent('text/plain',sessionId)
except Exception,error:
self.SendError(error)
def get(self,sessionId):
try:
if self.request.path.startswith('/request2'):
textMessage = myServer.GetMsg(sessionId)
self.SendContent('text/plain',textMessage)
except Exception,error:
self.SendError(error)
def SendContent(self,contentType,contentData):
self.response.set_status(200)
self.response.headers['content-type'] = contentType
self.response.headers['cache-control'] = 'no-cache'
self.response.write(contentData)
def SendError(self,error):
self.response.set_status(500)
self.response.write(error.message)
Here is the internal implementation of my server:
class MyServer():
def __init__(self):
self.sessions = SessionsTable.ReadSessions()
def GetNewSessionId(self):
while True:
sessionId = ... # a 16-digit random number
if SessionsTable.ReserveSession(sessionId):
self.sessions[sessionId] = ... # a text message
SessionsTable.WriteSession(self.sessions,sessionId)
return sessionId
def GetMsg(self,sessionId):
return self.sessions[sessionId]
And finally, here is the data-base maintenance within my server:
from google.appengine.ext import db
class SessionsTable(db.Model):
message = db.TextProperty()
@staticmethod
def ReadSessions():
sessions = {}
for session in SessionsTable.all():
sessions[session.key().name()] = session.message
return sessions
@staticmethod
@db.transactional
def ReserveSession(sessionId):
if not SessionsTable.get_by_key_name(sessionId):
SessionsTable(key_name=sessionId,message='').put()
return True
return False
@staticmethod
def WriteSession(sessions,sessionId):
SessionsTable(key_name=sessionId,message=sessions[sessionId]).put()
@staticmethod
def EraseSession(sessionId):
SessionsTable.get_by_key_name(sessionId).delete()
The exception itself indicates an illegal access to the sessions
dictionary using the sessionId
key. From my observation, it occurs only when the client-server sequence described at the beginning of this question is initiated after the server "has been sleeping" for a relatively long period of time (like a few days or so). It may provide some sort of clue to the source of this problem, though I am unable to see it.
My questions:
Is there anything visibly wrong with my design?
Has anyone experienced a similar problem on GAE?
Does anyone see an obvious solution, or even a debugging method that might help to understand this problem?
Thanks
You are wrongly assuming that all requests are handled by the same instance. This isn't at all the case: GAE, like most hosting enviroments, makes no guarantee about which server process will handle any request.
Your implementation is using a module-level variable, myServer
, which is a class instance with its own instance variables. But each server process will have its own instance of myServer, and they're not shared between processes: so a dictionary entry that's created in one request won't necessarily exist in a second.
You'll need to look at ways of persisting this data across instances. Really, that's what the datastore is for in first place; if you're worried about overhead, you should investigate using memcache.