Issue Details (XML | Word | Printable)

Key: MODPYTHON-57
Type: Bug Bug
Status: Closed Closed
Resolution: Fixed
Priority: Minor Minor
Assignee: Nicolas Lehuen
Reporter: Shack Toms
Votes: 0
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
mod_python

mem_cleanup throws "dictionary changed size during iteration"

Created: 28/May/05 05:33 AM   Updated: 05/Mar/06 02:03 PM
Return to search
Component/s: core
Affects Version/s: 3.1.4
Fix Version/s: 3.2.7

Time Tracking:
Not Specified

Environment: We are running under Windows, but the problem is likely not O/S dependent.

Resolution Date: 28/May/05 06:47 PM


 Description  « Hide
The mem_cleanup routine, in Session.py, appears to have a bug that causes
python to throw "dictionary changed size during iteration".

The current code is.

def mem_cleanup(sdict):
    for sid in sdict:
        dict = sdict[sid]
        if (time.time() - dict["_accessed"]) > dict["_timeout"]:
            del sdict[sid]

The for statement should be changed to
    for sid in sdict.keys():

This will first generate a list of the keys of the sdict, and will avoid the
complaint about the dictionary changing on the "del sdict[sid]" statement.

The dbm_cleanup code has another approach, to use one iteration to gather
the keys to be deleted, and then have a second iteration over the gathered
keys which does the del. Using that approach, mem_cleanup should become...

def mem_cleanup(sdict):
    old = []
    for sid in sdict:
        dict = sdict[sid]
        if (time.time() - dict["_accessed"]) > dict["_timeout"]:
            old.append(sid)
    for sid in old:
        del sdict[sid]

This is more efficient if the number of sessions is large compared to the
number of sessions to be deleted.


 All   Comments   Work Log   Change History   Subversion Commits      Sort Order: Ascending order - Click to sort in descending order
Nicolas Lehuen added a comment - 28/May/05 06:45 PM
We also have to protect against the case where some sessions are added to the sdict dictionary while the cleanup is being performed, or even against the case where two cleanups are performed concurrently. Hence, the same locking mechanisms that were used on DbmSession and FileSession should be applied here.

Fortunately we can rely on the GIL so that a call to sdict.keys() returns a snapshot of the SIDs at one point, so that even if some new session are added, we won't have a "dictionary changed size during iteration" exception, then protect against KeyError with a try..except block, in case another cleanup has deleted the session while we were examining it.

The final version is therefore :

def mem_cleanup(sdict):
    for sid in sdict.keys():
        try:
            session = sdict[sid]
            if (time.time() - session["_accessed"]) > session["_timeout"]:
                del sdict[sid]
        except:
            pass

This will do the trick, but a really clean implementation would rely on global locks, just like DbmSession and FileSession do.