Skip to content

Sqlite cursor garbage-collection issue with Python 3.11.0b3 #94028

Closed
@coleifer

Description

Bug report

This was reported to me on coleifer/peewee#2580 and, as I've managed to reproduce the issue, was asked by @erlend-aasland to submit a ticket here. Note that this issue does not manifest on other versions of Python (2.7, 3.6, 3.9 and 3.10) -- it appears to be a new issue on 3.11.0b3

The original reporter bisected and found the following commit introduced the regression: 3df0fc8

I believe this is a cursor garbage collection issue, though I may be mistaken. Keeping the cursors around in memory seems to cause the problem to manifest, but if you comment-out the line indicated below then the tests will pass. Note that Peewee doesn't do anything weird like keep the cursors in memory, this was just the first way I've been able to successfully reproduce the issue.

The test below does the following:

  • create a 1st connection to a sqlite db and put some rows into it, committing changes
  • create a 2nd connection to that db and verify we can see the rows, then delete them, committing changes
  • begin a transaction in 1st connection, inserting 2 new rows
  • verify the 2nd connection cannot see the uncommitted rows (this is where the failure occurs)
  • verify the 1st connection can see its uncommitted rows, then commit
  • lastly verify the 2nd connection can see the now-committed rows

What is doubly-confusing about this error is that, when the failure occurs, the SQL being executed should definitely NOT be returning None from the call to fetchone(). If the table did not exist, we would get a different error from Sqlite. If the table does exist and we just can't see any rows, then we should be getting 0 as the test asserts. Instead, the fetchone() is returning None.

Reproduce:

import glob
import os
import sqlite3

filename = '/tmp/test.db'
for f in glob.glob(filename + '*'):
    os.unlink(f)  # Cleanup anything from prev run(s).

CURSORS = {}

def sql(conn, sql, *params):
    curs = conn.cursor()
    curs.execute(sql, params)
    CURSORS[id(sql)] = curs  # COMMENT THIS OUT AND TEST WILL PASS.
    return curs

# Set up database w/some sample rows. Peewee sets isolation-level to None as we
# want to manage all transaction state ourselves, rather than use sqlite3's
# somewhat unusual semantics.
db = sqlite3.connect(filename, isolation_level=None)
db.execute('create table users (id integer not null primary key, '
           'username text not null)')
sql(db, 'insert into users (username) values (?), (?), (?)', 'u1', 'u2', 'u3')
db.commit()

# On 2nd connection verify rows are visible, then delete them.
new_db2 = sqlite3.connect(filename, isolation_level=None)
assert sql(new_db2, 'select count(*) from users').fetchone()[0] == 3
assert sql(new_db2, 'delete from users').rowcount == 3
new_db2.commit()

# Back in original connection, create 2 new users.
sql(db, 'begin')
sql(db, 'insert into users (username) values (?)', 'u4')
sql(db, 'insert into users (username) values (?)', 'u5')

# 2nd connection cannot see uncommitted changes.
# NOTE: this is the line that fails.
assert sql(new_db2, 'select count(*) from users').fetchone()[0] == 0

# Original conn can see its own changes.
assert sql(db, 'select count(*) from users').fetchone()[0] == 2
db.commit()

# Now the 2nd conn can see the changes.
assert sql(new_db2, 'select count(*) from users').fetchone()[0] == 2

On 3.11.0b3 the above fails on the indicated line with the following exception:

Traceback (most recent call last):
  File "/home/charles/tmp/py311/repro.py", line 40, in <module>
    assert sql(new_db2, 'select count(*) from users').fetchone()[0] == 0
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
TypeError: 'NoneType' object is not subscriptable

Your environment

  • CPython versions tested on: 2.7, 3.6, 3.9, 3.10 (passing), and 3.11.0b3 (failing)
  • Sqlite3 version 3.35 and 3.38
  • Linux / Debian stable

Metadata

Projects

  • Status

    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions