bpo-42213: Remove redundant cyclic GC hack in sqlite3 (GH-26517)

The sqlite3 module now fully implements the GC protocol, so there's no
need for this workaround anymore.

- Add and use managed resource helper for connections using TESTFN
- Sort test imports
- Split open-tests into their own test case

Automerge-Triggered-By: GH:vstinner
This commit is contained in:
Erlend Egeberg Aasland 2021-06-03 18:38:19 +02:00 committed by GitHub
parent 2c1e2583fd
commit d88b47b5a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 37 additions and 39 deletions

View File

@ -20,14 +20,26 @@
# misrepresented as being the original software. # misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution. # 3. This notice may not be removed or altered from any source distribution.
import threading import contextlib
import unittest
import sqlite3 as sqlite import sqlite3 as sqlite
import sys import sys
import threading
import unittest
from test.support.os_helper import TESTFN, unlink from test.support.os_helper import TESTFN, unlink
# Helper for tests using TESTFN
@contextlib.contextmanager
def managed_connect(*args, **kwargs):
cx = sqlite.connect(*args, **kwargs)
try:
yield cx
finally:
cx.close()
unlink(TESTFN)
class ModuleTests(unittest.TestCase): class ModuleTests(unittest.TestCase):
def test_api_level(self): def test_api_level(self):
self.assertEqual(sqlite.apilevel, "2.0", self.assertEqual(sqlite.apilevel, "2.0",
@ -190,26 +202,27 @@ class ConnectionTests(unittest.TestCase):
with self.assertRaises(AttributeError): with self.assertRaises(AttributeError):
self.cx.in_transaction = True self.cx.in_transaction = True
class OpenTests(unittest.TestCase):
_sql = "create table test(id integer)"
def test_open_with_path_like_object(self): def test_open_with_path_like_object(self):
""" Checks that we can successfully connect to a database using an object that """ Checks that we can successfully connect to a database using an object that
is PathLike, i.e. has __fspath__(). """ is PathLike, i.e. has __fspath__(). """
self.addCleanup(unlink, TESTFN)
class Path: class Path:
def __fspath__(self): def __fspath__(self):
return TESTFN return TESTFN
path = Path() path = Path()
with sqlite.connect(path) as cx: with managed_connect(path) as cx:
cx.execute('create table test(id integer)') cx.execute(self._sql)
def test_open_uri(self): def test_open_uri(self):
self.addCleanup(unlink, TESTFN) with managed_connect(TESTFN) as cx:
with sqlite.connect(TESTFN) as cx: cx.execute(self._sql)
cx.execute('create table test(id integer)') with managed_connect(f"file:{TESTFN}", uri=True) as cx:
with sqlite.connect('file:' + TESTFN, uri=True) as cx: cx.execute(self._sql)
cx.execute('insert into test(id) values(0)') with self.assertRaises(sqlite.OperationalError):
with sqlite.connect('file:' + TESTFN + '?mode=ro', uri=True) as cx: with managed_connect(f"file:{TESTFN}?mode=ro", uri=True) as cx:
with self.assertRaises(sqlite.OperationalError): cx.execute(self._sql)
cx.execute('insert into test(id) values(1)')
class CursorTests(unittest.TestCase): class CursorTests(unittest.TestCase):

View File

@ -254,11 +254,15 @@ class TraceCallbackTests(unittest.TestCase):
self.addCleanup(unlink, TESTFN) self.addCleanup(unlink, TESTFN)
con1 = sqlite.connect(TESTFN, isolation_level=None) con1 = sqlite.connect(TESTFN, isolation_level=None)
con2 = sqlite.connect(TESTFN) con2 = sqlite.connect(TESTFN)
con1.set_trace_callback(trace) try:
cur = con1.cursor() con1.set_trace_callback(trace)
cur.execute(queries[0]) cur = con1.cursor()
con2.execute("create table bar(x)") cur.execute(queries[0])
cur.execute(queries[1]) con2.execute("create table bar(x)")
cur.execute(queries[1])
finally:
con1.close()
con2.close()
self.assertEqual(traced_statements, queries) self.assertEqual(traced_statements, queries)

View File

@ -97,9 +97,6 @@ pysqlite_cache_init(pysqlite_Cache *self, PyObject *args, PyObject *kwargs)
} }
self->factory = Py_NewRef(factory); self->factory = Py_NewRef(factory);
self->decref_factory = 1;
return 0; return 0;
} }
@ -108,9 +105,7 @@ cache_traverse(pysqlite_Cache *self, visitproc visit, void *arg)
{ {
Py_VISIT(Py_TYPE(self)); Py_VISIT(Py_TYPE(self));
Py_VISIT(self->mapping); Py_VISIT(self->mapping);
if (self->decref_factory) { Py_VISIT(self->factory);
Py_VISIT(self->factory);
}
pysqlite_Node *node = self->first; pysqlite_Node *node = self->first;
while (node) { while (node) {
@ -124,9 +119,7 @@ static int
cache_clear(pysqlite_Cache *self) cache_clear(pysqlite_Cache *self)
{ {
Py_CLEAR(self->mapping); Py_CLEAR(self->mapping);
if (self->decref_factory) { Py_CLEAR(self->factory);
Py_CLEAR(self->factory);
}
/* iterate over all nodes and deallocate them */ /* iterate over all nodes and deallocate them */
pysqlite_Node *node = self->first; pysqlite_Node *node = self->first;

View File

@ -52,10 +52,6 @@ typedef struct
pysqlite_Node* first; pysqlite_Node* first;
pysqlite_Node* last; pysqlite_Node* last;
/* if set, decrement the factory function when the Cache is deallocated.
* this is almost always desirable, but not in the pysqlite context */
int decref_factory;
} pysqlite_Cache; } pysqlite_Cache;
extern PyTypeObject *pysqlite_NodeType; extern PyTypeObject *pysqlite_NodeType;

View File

@ -149,14 +149,6 @@ pysqlite_connection_init(pysqlite_Connection *self, PyObject *args,
return -1; return -1;
} }
/* By default, the Cache class INCREFs the factory in its initializer, and
* decrefs it in its deallocator method. Since this would create a circular
* reference here, we're breaking it by decrementing self, and telling the
* cache class to not decref the factory (self) in its deallocator.
*/
self->statement_cache->decref_factory = 0;
Py_DECREF(self);
self->detect_types = detect_types; self->detect_types = detect_types;
self->timeout = timeout; self->timeout = timeout;
(void)sqlite3_busy_timeout(self->db, (int)(timeout*1000)); (void)sqlite3_busy_timeout(self->db, (int)(timeout*1000));