bpo-45613: Set `sqlite3.threadsafety` dynamically (GH-29227)

Use the compile-time selected default SQLite threaded mode to set the
DB-API 2.0 attribute 'threadsafety'

Mappings:
  - SQLITE_THREADSAFE=0 => threadsafety=0
  - SQLITE_THREADSAFE=1 => threadsafety=3
  - SQLITE_THREADSAFE=2 => threadsafety=1
This commit is contained in:
Erlend Egeberg Aasland 2021-11-03 22:01:37 +01:00 committed by GitHub
parent 762173c670
commit c273986711
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 18 deletions

View File

@ -159,23 +159,41 @@ Module functions and constants
.. data:: threadsafety
Integer constant required by the DB-API, stating the level of thread safety
the :mod:`sqlite3` module supports. Currently hard-coded to ``1``, meaning
*"Threads may share the module, but not connections."* However, this may not
always be true. You can check the underlying SQLite library's compile-time
threaded mode using the following query::
Integer constant required by the DB-API 2.0, stating the level of thread
safety the :mod:`sqlite3` module supports. This attribute is set based on
the default `threading mode <https://sqlite.org/threadsafe.html>`_ the
underlying SQLite library is compiled with. The SQLite threading modes are:
import sqlite3
con = sqlite3.connect(":memory:")
con.execute("""
select * from pragma_compile_options
where compile_options like 'THREADSAFE=%'
""").fetchall()
1. **Single-thread**: In this mode, all mutexes are disabled and SQLite is
unsafe to use in more than a single thread at once.
2. **Multi-thread**: In this mode, SQLite can be safely used by multiple
threads provided that no single database connection is used
simultaneously in two or more threads.
3. **Serialized**: In serialized mode, SQLite can be safely used by
multiple threads with no restriction.
Note that the `SQLITE_THREADSAFE levels
<https://sqlite.org/compile.html#threadsafe>`_ do not match the DB-API 2.0
``threadsafety`` levels.
The mappings from SQLite threading modes to DB-API 2.0 threadsafety levels
are as follows:
+------------------+-----------------+----------------------+-------------------------------+
| SQLite threading | `threadsafety`_ | `SQLITE_THREADSAFE`_ | DB-API 2.0 meaning |
| mode | | | |
+==================+=================+======================+===============================+
| single-thread | 0 | 0 | Threads may not share the |
| | | | module |
+------------------+-----------------+----------------------+-------------------------------+
| multi-thread | 1 | 2 | Threads may share the module, |
| | | | but not connections |
+------------------+-----------------+----------------------+-------------------------------+
| serialized | 3 | 1 | Threads may share the module, |
| | | | connections and cursors |
+------------------+-----------------+----------------------+-------------------------------+
.. _threadsafety: https://www.python.org/dev/peps/pep-0249/#threadsafety
.. _SQLITE_THREADSAFE: https://sqlite.org/compile.html#threadsafe
.. versionchanged:: 3.11
Set *threadsafety* dynamically instead of hard-coding it to ``1``.
.. data:: PARSE_DECLTYPES

View File

@ -254,6 +254,10 @@ sqlite3
setting and getting SQLite limits by connection basis.
(Contributed by Erlend E. Aasland in :issue:`45243`.)
* :mod:`sqlite3` now sets :attr:`sqlite3.threadsafety` based on the default
threading mode the underlying SQLite library has been compiled with.
(Contributed by Erlend E. Aasland in :issue:`45613`.)
threading
---------

View File

@ -28,8 +28,6 @@ from _sqlite3 import *
paramstyle = "qmark"
threadsafety = 1
apilevel = "2.0"
Date = datetime.date

View File

@ -54,8 +54,9 @@ class ModuleTests(unittest.TestCase):
"apilevel is %s, should be 2.0" % sqlite.apilevel)
def test_thread_safety(self):
self.assertEqual(sqlite.threadsafety, 1,
"threadsafety is %d, should be 1" % sqlite.threadsafety)
self.assertIn(sqlite.threadsafety, {0, 1, 3},
"threadsafety is %d, should be 0, 1 or 3" %
sqlite.threadsafety)
def test_param_style(self):
self.assertEqual(sqlite.paramstyle, "qmark",

View File

@ -0,0 +1,3 @@
:mod:`sqlite3` now sets :attr:`sqlite3.threadsafety` based on the default
threading mode the underlying SQLite library has been compiled with. Patch by
Erlend E. Aasland.

View File

@ -540,6 +540,28 @@ add_integer_constants(PyObject *module) {
return 0;
}
/* Convert SQLite default threading mode (as set by the compile-time constant
* SQLITE_THREADSAFE) to the corresponding DB-API 2.0 (PEP 249) threadsafety
* level. */
static int
get_threadsafety(pysqlite_state *state)
{
int mode = sqlite3_threadsafe();
switch (mode) {
case 0: // Single-thread mode; threads may not share the module.
return 0;
case 1: // Serialized mode; threads may share the module,
return 3; // connections, and cursors.
case 2: // Multi-thread mode; threads may share the module, but not
return 1; // connections.
default:
PyErr_Format(state->InterfaceError,
"Unable to interpret SQLite threadsafety mode. Got %d, "
"expected 0, 1, or 2", mode);
return -1;
}
}
static int
module_traverse(PyObject *module, visitproc visit, void *arg)
{
@ -689,6 +711,14 @@ module_exec(PyObject *module)
goto error;
}
int threadsafety = get_threadsafety(state);
if (threadsafety < 0) {
goto error;
}
if (PyModule_AddIntConstant(module, "threadsafety", threadsafety) < 0) {
goto error;
}
/* initialize microprotocols layer */
if (pysqlite_microprotocols_init(module) < 0) {
goto error;