Opened 16 years ago
Last modified 2 years ago
#9298 new defect
IntegrityError: duplicate key value violates unique constraint "session_attribute_pk"
| Reported by: | Christian Boos | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | next-stable-1.6.x | 
| Component: | general | Version: | 0.12b1 | 
| Severity: | minor | Keywords: | session | 
| Cc: | Mitar | Branch: | |
| Release Notes: | |||
| API Changes: | |||
| Internal Changes: | |||
Description
Happened when loading a bunch of reports as anonymous.
Information système
 Trac  |  0.12b1 
 | 
 Babel  |  0.9.5 
 | 
 Docutils  |  0.6 
 | 
 Genshi  |  0.6 
 | 
 psycopg2  |  2.0.8 
 | 
 Pygments  |  1.2.2dev-20100224 
 | 
 Python  |  2.5.2 (r252:60911, Oct  5 2008, 19:29:17)  [GCC 4.3.2] 
 | 
 pytz  |  2010g 
 | 
 setuptools  |  0.6c9 
 | 
 Subversion  |  1.5.1 (r32289) 
 | 
Pile d'appel Python
Traceback (most recent call last):
  File "build/bdist.linux-x86_64/egg/trac/web/main.py", line 512, in _dispatch_request
    dispatcher.dispatch(req)
  File "build/bdist.linux-x86_64/egg/trac/web/main.py", line 257, in dispatch
    req.session.save()
  File "build/bdist.linux-x86_64/egg/trac/web/session.py", line 88, in save
    @self.env.with_transaction()
  File "build/bdist.linux-x86_64/egg/trac/db/util.py", line 60, in transaction_wrapper
    fn(ldb)
  File "build/bdist.linux-x86_64/egg/trac/web/session.py", line 117, in delete_session_cookie
    """, attrs)
  File "build/bdist.linux-x86_64/egg/trac/db/util.py", line 142, in executemany
    return self.cursor.executemany(sql_escape_percent(sql), args)
IntegrityError: duplicate key value violates unique constraint "session_attribute_pk"
      Attachments (0)
Change History (10)
comment:1 by , 15 years ago
comment:2 by , 15 years ago
comment:3 by , 14 years ago
| Cc: | added | 
|---|
comment:4 by , 14 years ago
I think that the problem basically lies in the session update code itself:
125	            if not skip and self._old != self:
126	                if not items and not authenticated:
127	                    # No need to keep around empty unauthenticated sessions
128	                    db("DELETE FROM session WHERE sid=%s AND authenticated=0",
129	                       (self.sid,))
130	                db("""DELETE FROM session_attribute
131	                      WHERE sid=%s AND authenticated=%s
132	                      """, (self.sid, authenticated))
133	                self._old = dict(self.items())
134	                db.executemany("""
135	                    INSERT INTO session_attribute
136	                      (sid,authenticated,name,value)
137	                    VALUES (%s,%s,%s,%s)
138	                    """, [(self.sid, authenticated, k, v) for k, v in items])
139	                session_saved = True
- Request 1 is processed, which will lock the db and delete all session attributes available and then it starts inserting the "new" attributes
 - Request 2 is processed, which in turn will also delete all session attributes currently available and it will start inserting the "new" attributes
 
So, while request 1 processing already removed the attributes and begun inserting the new ones, request 2 again removes the newly inserted session attributes and also begins inserting the same session attributes, eventually coming to a point where Thread/Process 1 just inserted the same attribute, which will then result in the experienced duplicate key exception.
Now, in pysqlite, the executemany() will be executed on a per parameter basis, so instead of inserting the bunch of parameters in one go, it will iterate over the existing parameters, see http://code.google.com/p/pysqlite/source/browse/src/cursor.c#640 where it reads
    while (1) {
        parameters = PyIter_Next(parameters_iter);
    ...
    }
I presume that the database will not be locked for as long as the operation is processed but on a per insert statement basis.
One possible remedy I see here is to refactor the session handling code so that it will first create a difference set of the attributes in the session that have actually changed and then will delete and reinsert these attributes individually. The overhead is negligible due to the fact that the underlying pysqlite implementation also processes each insert statement on an individual basis.
comment:5 by , 14 years ago
Of course, adding a try/except around the offending lines might do the trick, but it will not solve the inherent problem. See the regression mentioned in comment:2 induced by changeset:9342.
Here, you expect the exception and also loss of more current session data, if you reintroduce the try/except statements instead of only delete/insert the data that has actually changed.
comment:6 by , 14 years ago
The whole session update is wrapped in a transaction, so it should be atomic. But somehow, that's not the case.
comment:7 by , 11 years ago
| Milestone: | next-minor-0.12.x → next-stable-1.0.x | 
|---|
comment:8 by , 9 years ago
| Milestone: | next-stable-1.0.x → next-stable-1.2.x | 
|---|
Moved ticket assigned to next-stable-1.0.x since maintenance of 1.0.x is coming to a close. Please move the ticket back if it's critical to fix on 1.0.x.
comment:9 by , 5 years ago
| Milestone: | next-stable-1.2.x → next-stable-1.4.x | 
|---|



  
Similar to #7663? We are investigating having this same problem, so maybe it's not as closed as that ticket suggests (or a regression.)