Edgewall Software
Modify

Opened 3 months ago

Closed 2 months ago

Last modified 8 weeks ago

#13505 closed defect (fixed)

Trac dies with segmentation fault in Python 3.11.0

Reported by: seino Owned by: Jun Omae
Priority: normal Milestone: 1.5.4
Component: database backend Version:
Severity: critical Keywords: python3.11 sqlite
Cc: Branch:
Release Notes:

Fixed SEGV raised from SQLite cursor with Python 3.11.

API Changes:
Internal Changes:

Description

In existence Trac environments /path/to/tracenv, tracd dies with segmentation fault.

$ python3 --version
Python 3.11.0
$ cd /path/to/tracenv
$ tracd -p 8000 .
Server starting in PID 2152.
Serving on 0.0.0.0:8000 view at http://127.0.0.1:8000/
Using HTTP/1.1 protocol version
127.0.0.1 - - [26/Nov/2022 09:53:15] "GET / HTTP/1.1" 200 -
Segmentation fault (core dumped)
$ 

I use:

  • Trac 1.5.4.dev0 (revision 17604)
  • Jinja2 3.1.2
  • Babel 2.11.0
  • MarkupSafe 2.1.1
  • Fedora 37 (both of x86_64 and aarch64)

Attachments (0)

Change History (10)

comment:1 by seino, 3 months ago

I try to create a newly trac environment, trac-admin also dies.

$ trac-admin . initenv
Creating a new Trac environment at /path/to/tracenv

Trac will first ask a few questions about your environment
in order to initialize and prepare the project database.

 Please enter the name of your project.
 This name will be used in page titles and descriptions.

Project Name [My Project]> 
 Please specify the connection string for the database. By default,
 a local SQLite database is created in the environment directory.
 It is also possible to use an existing MySQL or PostgreSQL database
 (check the Trac documentation for the connection string syntax).

Database connection string [sqlite:db/trac.db]> 
Segmentation fault (core dumped)
$ 

comment:2 by Jun Omae, 3 months ago

Keywords: python3.11 added
Owner: set to Jun Omae
Status: newassigned

Thanks for the reporting! That's pretty bad. I just tried to reproduce it and got the same. Investigating…

comment:3 by Jun Omae, 3 months ago

It seems that the issue is related to sqlite cursor.

$ gdb --args /dev/shm/trac-py311/bin/python3.11-dbg -m trac.admin.console /dev/shm/tracenv-py311 initenv Project sqlite:db/trac.db
...
(gdb) bt
#0  Py_DECREF (op=<unknown at remote 0xdddddddddddddddd>, lineno=5566, filename=0x780168 "../Objects/dictobject.c") at ../Include/object.h:521
#1  _PyObject_ClearInstanceAttributes (self=self@entry=<EagerCursor(pos=<unknown at remote 0xdddddddddddddddd>, cnx=<unknown at remote 0xdddddddddddddddd>) at remote 0x7ffff51160e0>) at ../Objects/dictobject.c:5566
#2  0x000000000052e9ad in subtype_clear (self=<EagerCursor(pos=<unknown at remote 0xdddddddddddddddd>, cnx=<unknown at remote 0xdddddddddddddddd>) at remote 0x7ffff51160e0>) at ../Objects/typeobject.c:1288
#3  0x00007ffff59d8a9c in cursor_dealloc (self=self@entry=0x7ffff51160e0) at ./Modules/_sqlite/cursor.c:184
#4  0x0000000000531149 in subtype_dealloc (self=<EagerCursor(pos=<unknown at remote 0xdddddddddddddddd>, cnx=<unknown at remote 0xdddddddddddddddd>) at remote 0x7ffff51160e0>) at ../Objects/typeobject.c:1472
#5  0x0000000000514a02 in _Py_Dealloc (op=<optimized out>) at ../Objects/object.c:2389
#6  0x000000000052dc10 in Py_DECREF (op=<optimized out>, lineno=1262, filename=0x78bd03 "../Objects/typeobject.c") at ../Include/object.h:527
#7  clear_slots (type=type@entry=0x137b720, self=self@entry=<IterableCursor at remote 0x7ffff51adea0>) at ../Objects/typeobject.c:1262
#8  0x0000000000530e9f in subtype_dealloc (self=<IterableCursor at remote 0x7ffff51adea0>) at ../Objects/typeobject.c:1428
#9  0x0000000000514a02 in _Py_Dealloc (op=<optimized out>) at ../Objects/object.c:2389
#10 0x00000000005fedb7 in Py_DECREF (op=<optimized out>, lineno=602, filename=0x752b35 "../Include/object.h") at ../Include/object.h:527
#11 Py_XDECREF (op=<optimized out>) at ../Include/object.h:602
#12 _PyFrame_Clear (frame=frame@entry=0x7ffff7fc0980) at ../Python/frame.c:131
#13 0x00000000005c233b in _PyEvalFrameClearAndPop (tstate=tstate@entry=0xb476f8 <_PyRuntime+166328>, frame=frame@entry=0x7ffff7fc0980) at ../Python/ceval.c:6400
#14 0x00000000005dc7ce in _PyEval_Vector (tstate=0xb476f8 <_PyRuntime+166328>, func=<optimized out>, locals=locals@entry=0x0, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>) at ../Python/ceval.c:6433
#15 0x00000000004b981c in _PyFunction_Vectorcall (func=<optimized out>, stack=<optimized out>, nargsf=<optimized out>, kwnames=<optimized out>) at ../Objects/call.c:393
#16 0x00000000004b9c7d in _PyObject_FastCallDictTstate (tstate=tstate@entry=0xb476f8 <_PyRuntime+166328>, callable=callable@entry=<function at remote 0x7ffff5a0adb0>, args=args@entry=0x7fffffffd5c0, nargsf=nargsf@entry=3, kwargs=kwargs@entry=0x0) at ../Objects/call.c:141
#17 0x00000000004b9f29 in _PyObject_Call_Prepend (tstate=tstate@entry=0xb476f8 <_PyRuntime+166328>, callable=callable@entry=<function at remote 0x7ffff5a0adb0>, obj=obj@entry=<ConnectionWrapper at remote 0x7ffff5166530>, args=args@entry=('\n                    SELECT value FROM `system` WHERE name=%s\n                    ', ('initial_database_version',)), kwargs=kwargs@entry=0x0) at ../Objects/call.c:482
#18 0x0000000000537368 in slot_tp_call (self=<ConnectionWrapper at remote 0x7ffff5166530>, args=('\n                    SELECT value FROM `system` WHERE name=%s\n                    ', ('initial_database_version',)), kwds=0x0) at ../Objects/typeobject.c:7630
#19 0x00000000004b9ad6 in _PyObject_MakeTpCall (tstate=tstate@entry=0xb476f8 <_PyRuntime+166328>, callable=callable@entry=<ConnectionWrapper at remote 0x7ffff5166530>, args=args@entry=0x7ffff7fc0958, nargs=<optimized out>, keywords=keywords@entry=0x0) at ../Objects/call.c:214
#20 0x00000000004ba446 in _PyObject_VectorcallTstate (kwnames=0x0, nargsf=9223372036854775810, args=0x7ffff7fc0958, callable=<ConnectionWrapper at remote 0x7ffff5166530>, tstate=0xb476f8 <_PyRuntime+166328>) at ../Include/internal/pycore_call.h:90
#21 PyObject_Vectorcall (callable=callable@entry=<ConnectionWrapper at remote 0x7ffff5166530>, args=args@entry=0x7ffff7fc0958, nargsf=<optimized out>, kwnames=kwnames@entry=0x0) at ../Objects/call.c:299
#22 0x00000000005d758f in _PyEval_EvalFrameDefault (tstate=tstate@entry=0xb476f8 <_PyRuntime+166328>, frame=0x7ffff7fc08d8, frame@entry=0x7ffff7fc0638, throwflag=throwflag@entry=0) at ../Python/ceval.c:4772
#23 0x00000000005dc7e6 in _PyEval_EvalFrame (throwflag=0, frame=0x7ffff7fc0638, tstate=0xb476f8 <_PyRuntime+166328>) at ../Include/internal/pycore_ceval.h:73
#24 _PyEval_Vector (tstate=0xb476f8 <_PyRuntime+166328>, func=<optimized out>, locals=locals@entry=0x0, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>) at ../Python/ceval.c:6428
#25 0x00000000004b981c in _PyFunction_Vectorcall (func=<optimized out>, stack=<optimized out>, nargsf=<optimized out>, kwnames=<optimized out>) at ../Objects/call.c:393
#26 0x00000000004bcf1a in _PyObject_VectorcallTstate (kwnames=('create', 'options', 'default_data'), nargsf=2, args=0x7ffff5540440, callable=<function at remote 0x7ffff54e41b0>, tstate=0xb476f8 <_PyRuntime+166328>) at ../Include/internal/pycore_call.h:92
#27 method_vectorcall (method=<optimized out>, args=0x7ffff5540448, nargsf=<optimized out>, kwnames=('create', 'options', 'default_data')) at ../Objects/classobject.c:59
#28 0x00000000004b93dc in _PyVectorcall_Call (tstate=tstate@entry=0xb476f8 <_PyRuntime+166328>, func=0x4bcd1d <method_vectorcall>, callable=callable@entry=<method at remote 0x7ffff5c7cb90>, tuple=tuple@entry=('/dev/shm/tracenv-py311',), kwargs=kwargs@entry={'create': True, 'options': [('project', 'name', 'Project'), ('trac', 'database', 'sqlite:db/trac.db')], 'default_data': True}) at ../Objects/call.c:257
#29 0x00000000004b973d in _PyObject_Call (tstate=0xb476f8 <_PyRuntime+166328>, callable=callable@entry=<method at remote 0x7ffff5c7cb90>, args=args@entry=('/dev/shm/tracenv-py311',), kwargs=kwargs@entry={'create': True, 'options': [('project', 'name', 'Project'), ('trac', 'database', 'sqlite:db/trac.db')], 'default_data': True}) at ../Objects/call.c:328
#30 0x00000000004b97a1 in PyObject_Call (callable=callable@entry=<method at remote 0x7ffff5c7cb90>, args=args@entry=('/dev/shm/tracenv-py311',), kwargs=kwargs@entry={'create': True, 'options': [('project', 'name', 'Project'), ('trac', 'database', 'sqlite:db/trac.db')], 'default_data': True}) at ../Objects/call.c:355
...

comment:4 by Jun Omae, 2 months ago

Could you please try the following patch and feedback the result?

  • trac/db/sqlite_backend.py

    diff --git a/trac/db/sqlite_backend.py b/trac/db/sqlite_backend.py
    index b1b7782c5..321d0e844 100644
    a b class PyFormatCursor(sqlite.Cursor):  
    5252        try:
    5353            return function(self, *args, **kwargs)
    5454        except sqlite.DatabaseError:
    55             self.cnx.rollback()
     55            self.connection.rollback()
    5656            raise
    5757
    5858    def execute(self, sql, args=None):
    class SQLiteConnection(ConnectionBase, ConnectionWrapper):  
    326326    def cursor(self):
    327327        cursor = self.cnx.cursor((PyFormatCursor, EagerCursor)[self._eager])
    328328        self._active_cursors[cursor] = True
    329         cursor.cnx = self
    330329        return IterableCursor(cursor, self.log)
    331330
    332331    def rollback(self):

comment:5 by Jun Omae, 2 months ago

Component: generaldatabase backend
Keywords: sqlite added

comment:6 by Jun Omae, 2 months ago

I noticed the patch in comment:4 is incorrect. Instead, please try the following patch:

  • trac/db/sqlite_backend.py

    diff --git a/trac/db/sqlite_backend.py b/trac/db/sqlite_backend.py
    index b1b7782c5..c058e6840 100644
    a b min_sqlite_version = (3, 0, 0)  
    4848
    4949
    5050class PyFormatCursor(sqlite.Cursor):
     51
     52    __slots__ = ['cnx']
     53
    5154    def _rollback_on_error(self, function, *args, **kwargs):
    5255        try:
    5356            return function(self, *args, **kwargs)

comment:7 by seino, 2 months ago

Thank you for your quick response.

This patch (comment:6) seems to work well.

comment:8 by Jun Omae, 2 months ago

Release Notes: modified (diff)
Resolution: fixed
Status: assignedclosed

Thanks for the feedback. Confirmed that unit tests pass with Python 3.5 - 3.11. Fixed in [17605].

comment:9 by Jun Omae, 8 weeks ago

I think we should report this issue to https://github.com/python/cpython/issues, however I don't succeed to create minimal reproduction code yet.

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain Jun Omae.
The resolution will be deleted. Next status will be 'reopened'.
to The owner will be changed from Jun Omae to the specified user.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.