Skip to content

bpo-45613: Set sqlite3.threadsafety dynamically #29227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 35 additions & 17 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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::

import sqlite3
con = sqlite3.connect(":memory:")
con.execute("""
select * from pragma_compile_options
where compile_options like 'THREADSAFE=%'
""").fetchall()

Note that the `SQLITE_THREADSAFE levels
<https://sqlite.org/compile.html#threadsafe>`_ do not match the DB-API 2.0
``threadsafety`` levels.

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:

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.

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

Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,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
---------
Expand Down
2 changes: 0 additions & 2 deletions Lib/sqlite3/dbapi2.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@

paramstyle = "qmark"

threadsafety = 1

apilevel = "2.0"

Date = datetime.date
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/test_sqlite3/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ def test_api_level(self):
"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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
30 changes: 30 additions & 0 deletions Modules/_sqlite/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,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)
{
Expand Down Expand Up @@ -564,6 +586,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;
Expand Down