From: Steve S. <st...@ag...> - 2011-03-02 21:23:54
|
Hi, We ran across an interesting problem with sessions being automatically saved by Transaction.py. We had a situation where two AJAX requests were issued and one of the requests updates the session and it started and completed within the lifetime of another request. In that scenario the completion of the first request overwrites the session and the changes to the session made by the shorter request are lost. Here's an easier to follow timeline: Request 1 - reads session (assume session is empty) Request 2 - reads session (assume session is empty) Request 2 - adds data to session Request 2 - sleep() called - session is stored (session contains data) Request 1 - sleep() called - session is stored (session is now empty - the value of the session when Request 1 read it) So when later requests go to read the data stored in the session by request 2 the data isn't there. Here's the code from WebKit.Transaction: def sleep(self): """Send sleep() to the session and the servlet. Note that sleep() is sent in reverse order as awake() (which is typical for shutdown/cleanup methods). """ self._nested -= 1 self._servlet.sleep(self) if not self._nested and self._session: self._session.sleep(self) self._application.sessions().storeSession(self._session) We put a hack in the longer running AJAX page's sleep() to keep the session from being stored since it doesn't modify the session: def sleep(self, trans): trans._session = None Is there an official way to disable the automatic storage of sessions? If not there are a number of ways to resolve this problem more generally: - Explicit session saves by servlet - Transaction never calls storeSession() - Explicit opt in/opt out of session storage by servlet - Explicit opt in/opt out of session storage by AppConfig - Extend Session functionality to keep a "dirty" flag and only automatically save the session if it has been modified during the request/response. IMO Webware shouldn't be saving the session automatically at the end of each request. I'd rather the servlet logic explicitly decide when and if to save the session, but this would break backward compatibility. I'd be happy to supply a patch if we can decide on a way forward. Best Regards, Steve Blog: http://tech.agilitynerd.com/ |
From: Christoph Z. <ci...@on...> - 2011-03-03 15:28:05
|
Hi Steve, thanks for the detailed write-up; I see the problem. The current session handling is really not ideal when you're using AJAX (which is no wonder since Webware has been designed before anybody even thought about that). IMHO the most simple solution would be to keep a "dirty flag" (set whenever __setitem__ or __delitem__ is called for the session) and then save the session only when the dirty flag has been set. This will also be good for performance. We could also add an AppConfig setting for manual saving (False by default to maintain backward compatibility). In this case, the dirty flag would not be set automatically, but by calling a save() method on the session. But I'm already seeing a problem with that approach: Since the lastAccessTime is stored along with the session, it will not get updated if the session is not saved, so the session will expire too early if the user does not do anything that alters the session. We could also keep a "dirty set" instead of a dirty flag. This set would contain all keys of the session for which the value has been changed or deleted. When the session store saves the session, it would then merge the current session values with the dirty set. This would require an additional session read operation, though. And it would be a pretty substantial change to the current session store(s). I'd rather avoid such drastic changes at least for the upcoming 1.1 version. -- Christoph |
From: Steve S. <st...@ag...> - 2011-03-03 17:21:39
|
On Thu, Mar 3, 2011 at 9:28 AM, Christoph Zwerschke <ci...@on...> wrote: > IMHO the most simple solution would be to keep a "dirty flag" (set > whenever __setitem__ or __delitem__ is called for the session) and then > save the session only when the dirty flag has been set. This will also > be good for performance. We could also add an AppConfig setting for > manual saving (False by default to maintain backward compatibility). In > this case, the dirty flag would not be set automatically, but by calling > a save() method on the session. > > But I'm already seeing a problem with that approach: Since the > lastAccessTime is stored along with the session, it will not get updated > if the session is not saved, so the session will expire too early if the > user does not do anything that alters the session. > I kind of don't care if the lastAccessTime isn't updated in this session saving "mode". We use very long session expiry times so if a "read only" user had to relogin a little more frequently I don't think it would be an issue that anyone would notice. Also for our system at some point they will perform an action that modifies the session. Since this is a new feature which Webware users would have to turn on I'd recommend we do "the simplest thing that works" and provide a new config setting for Sessions that takes multiple values: "EveryRequest" - default and current functionality "WhenChanged" - only saves when add/delete has been called Best Regards, Steve |
From: Christoph Z. <ci...@on...> - 2011-03-03 20:23:32
|
Btw, which kind of session store are you using? The default DynamicStore is based on the MemoryStore, and here requests share the same session object. So the scenario you described should actually not cause a problem for this kind of session store. -- Christoph |
From: Steve S. <st...@ag...> - 2011-03-03 20:38:50
|
On Thu, Mar 3, 2011 at 2:23 PM, Christoph Zwerschke <ci...@on...> wrote: > Btw, which kind of session store are you using? The default DynamicStore > is based on the MemoryStore, and here requests share the same session > object. So the scenario you described should actually not cause a > problem for this kind of session store. > We use multiple appservers without session affinity so we use MemcacheSession to share sessions across appservers Best Regards, Steve |
From: Christoph Z. <ci...@on...> - 2011-03-03 21:31:49
|
Am 03.03.2011 21:38 schrieb Steve Schwarz: > We use multiple appservers without session affinity so we use > MemcacheSession to share sessions across appservers Ok, that explains why you've run into that issue. The session objects are not shared between threads in this case. So the simplest solution would be to keep a "dirty flag" and store the session only if that flag changed or when a save() method is called. I guess save() could actually be added as a synonym for storeSession() and in this method, the "dirty flag" should be reset of course. Not sure if we should make it the standard mode for the FileStore and the MemcacheStore, because it's not 100% backward compatible, particularly because the lastAccessTime will not be updated. Of course we could always update the lastAccessTime in case the session is not dirty, but this would require an additional read operation and decrease performance. -- Christoph |
From: Christoph Z. <ci...@on...> - 2011-03-04 09:59:31
|
Hi Steve, I have now implemented this option in the trunk, http://svn.w4py.org/Webware/trunk in r8156. You need to set AlwaysSaveSessions = False in Application.config to activate it. Let me know how it works as I want to release 1.1rc1 and then 1.1 as soon as possible. -- Christoph |