Ticket #187 (assigned defect)
Statement cache not thread-safe?
| Reported by: | christian.boos | Owned by: | gh |
|---|---|---|---|
| Priority: | high | Milestone: | |
| Component: | implementation | Version: | pysqlite-SVN |
| Severity: | critical | Keywords: | segfault statement cache |
| Cc: |
Description
In Trac, I've enabled connection pooling a while back (see [trac 3830]). That was working great to reduce (even eliminate) the infamous database is locked errors we had (trac:ticket:3446).
However, I should have tested more on Linux :/
On Windows, it's been working fine under heavy tests, for months now.
But on Linux, you very quickly run into the following crash (all it takes are 2 concurrent requests to the timeline):
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1086347616 (LWP 6335)]
0x0000002a98bdae3a in pysqlite_cursor_iternext (self=0x2a9b170628)
at cursor.c:864
864 rc = _sqlite_step_with_busyhandler(self->statement->st, self->connection);
(gdb) bt
#0 0x0000002a98bdae3a in pysqlite_cursor_iternext (self=0x2a9b170628)
at cursor.c:864
#1 0x0000002a98bdaef6 in pysqlite_cursor_fetchone (
self=<value optimized out>, args=<value optimized out>) at cursor.c:882
#2 0x0000002a9570a183 in PyEval_EvalFrame (f=0xc31300) at ceval.c:3542
#3 0x0000002a956b55dc in gen_iternext (gen=0x2a9b248a70) at genobject.c:47
#4 0x0000002a95705630 in PyEval_EvalFrame (f=0xc7aa10) at ceval.c:2121
.
.
.
and:
(gdb) p *self
$1 = {ob_refcnt = 2, ob_type = 0x9008b0, connection = 0x2a97d29e88,
description = 0x2a9b184050, row_cast_map = 0x2a9b193758, arraysize = 1,
lastrowid = 0x2a9587db10, rowcount = 0x509598, row_factory = 0x2a9587db10,
statement = 0x0, next_row = 0x0}
(therefore self->statement->st crashes)
The python frames are:
(gdb) f 2 #2 0x0000002a9570a183 in PyEval_EvalFrame (f=0xc31300) at ceval.c:3542 3542 C_TRACE(x, (*meth)(self,NULL)); (gdb) pyframe /packages/trac/sandbox/blame/trac/db/util.py (41): __iter__ (gdb) f 4 #4 0x0000002a95705630 in PyEval_EvalFrame (f=0xc7aa10) at ceval.c:2121 2121 x = (*v->ob_type->tp_iternext)(v); (gdb) pyframe /packages/trac/sandbox/blame/trac/versioncontrol/cache.py (56): get_changesets
this corresponds to a simple iteration over a result set:
def get_changesets(self, start, stop): cursor = self.db.cursor() cursor.execute("SELECT rev FROM revision " "WHERE time >= %s AND time < %s " "ORDER BY time", (to_timestamp(start), to_timestamp(stop))) for rev, in cursor: if self.authz.has_permission_for_changeset(rev): yield self.get_changeset(rev)
(line 56 is the for)
I used pysqlite trunk at current latest (r301).
