@@ -6,16 +6,18 @@ Concurrency & Async
66How SQLite concurrency works
77----------------------------
88
9- Each connection has a mutex to protect the SQLite data structure. It
10- is acquired on a call into the connection, and released on return of
11- the call. The mutex can be acquired more times in the same thread,
12- allowing nested calls, but cannot be acquired outside of the thread
13- until the top level call in the original thread completes.
9+ Each connection has a lock (`mutex
10+ <https://en.wikipedia.org/wiki/Lock_(computer_science)> `__ to protect
11+ the SQLite data structure. It is acquired on a call into the
12+ connection, and released on return of the call. The mutex can be
13+ acquired more times in the same thread, allowing nested calls, but
14+ cannot be acquired outside of the thread until the top level call in
15+ the original thread completes.
1416
1517This means you cannot get more concurrency per connection by using
1618additional threads, although SQLite can do so internally (`pragma
17- threads <https://www.sqlite.org/pragma.html#pragma_threads> `__)
18- such as for sorting. You can get concurrency with multiple connections.
19+ threads <https://www.sqlite.org/pragma.html#pragma_threads> `__) such
20+ as for sorting. You can get concurrency with multiple connections.
1921
2022SQLite is inherently synchronous due to being written in C and using
2123the C stack.
@@ -31,9 +33,9 @@ GIL (usual operation)
3133 second), with the operating system scheduler choosing which thread
3234 runs next.
3335
34- The GIL can be released by C code when not using Python data structures
35- to allow other threads to run. This is done during I/O operations
36- etc.
36+ The GIL can be released by C code when not using Python data
37+ structures to allow other threads Python to run. This is done
38+ during I/O and database operations etc.
3739
3840Free threaded (Python 3.14+)
3941
@@ -43,7 +45,8 @@ Free threaded (Python 3.14+)
4345 not using the same objects.
4446
4547 The extra locking can result in around a 50% performance hit
46- versus the single global lock in a single thread.
48+ versus the single global lock in a single thread. The operating
49+ system scheduler can run all the threads at the same time.
4750
4851Async
4952
5861 hit to throughput, but latencies and time to complete are
5962 far more uniform.
6063
64+ An async framework like :mod: `asyncio ` or `trio
65+ <https://trio.readthedocs.io> `__ runs the event loop and chooses
66+ which code to run next.
67+
6168How APSW works
6269--------------
6370
@@ -126,11 +133,11 @@ async mode or not, and behave appropriately. You can use
126133:attr:`Connection.is_async` to check.
127134
128135However to make type checkers and IDEs work better, the type stubs
129- included with APSW have those classes so it is clear when returned
130- values are direct, or need to be awaited.
136+ included with APSW have those synthetic classes so it is clear when
137+ returned values are direct, or need to be awaited.
131138
132- You can use :meth:`Connection.async_run` to run functions in the
133- async Connection worker thread.
139+ You can use :meth:`Connection.async_run` to run your own functions in
140+ the async Connection worker thread.
134141
135142.. _anyio_note:
136143
@@ -249,29 +256,22 @@ Async object
249256Close
250257!!!!!
251258
252- Sync object
253- Closes this object , releasing its held resources. It is safe to
254- call close multiple times. When you call close on a
255- :class :`Connection` , then it will close all the corresponding
256- objects like :class :`Cursor` , :class :`Blob` , :class :`Session` etc.
257-
258- Async object
259- You should :code:`await ` calling :code:`aclose` - the async
260- version of close. It is safe to call multiple times. It will
261- only be effective if the event loop is still running.
262- :func:`contextlib.aclosing` is a handy context manager.
259+ You can call close on sync ** and ** async objects. When you call close on a
260+ :class :`Connection` , then it will close all the corresponding
261+ objects like :class :`Cursor` , :class :`Blob` , :class :`Session` etc.
263262
264- This is important for the :class :`Connection` because the worker
265- thread will not exit until the connection is closed. It will be
266- closed if normal garbage collection happens, but it is very easy
267- to have a stray reference preventing that.
263+ It is safe to call :code:`close` and :code:`aclose` multiple times,
264+ even on already closed objects.`
268265
269- It is allowed to call :code:`close` on async objects, which will
270- immediately close them and return :exc:`ConnectionClosedError` to
271- any subsequent calls made. This is also the only way to close an
272- object after the event loop has finished. You can use
273- :func:`apsw.connections` to get the currently open connections.
266+ Sync object
267+ :code:`close` closes this object in the foreground, releasing its
268+ held resources.
274269
270+ Async object
271+ :code:`aclose` closes this object in the background. You should
272+ :code:`await ` calling :code:`aclose` to know when it has
273+ completed. :code:`close` will close it in the foreground
274+ which could take some time.
275275
276276Callbacks
277277======== =
@@ -339,8 +339,8 @@ Most configuration uses :mod:`contextvars`.
339339
340340:attr:`apsw.aio.deadline`
341341
342- When SQLite queries or async callbacks should timeout. ( | trio|
343- and | anyio| ) native timeout is also supported.
342+ When SQLite queries or async callbacks should timeout. | trio|
343+ and | anyio| ) native timeouts are also supported.
344344
345345:attr: `apsw.async_controller `
346346
@@ -392,7 +392,7 @@ The controller is responsible for:
392392* Sending calls from the event loop to the worker thread with
393393 awaitable results
394394* Checking deadlines and cancellations
395- * Running coroutines in the event loop, and providing their results
395+ * Forwarding coroutines in the event loop, and providing their results
396396* Stopping the worker thread when told about database close
397397
398398Although it seems like a lot, they are around 50 lines of code, and
@@ -411,12 +411,13 @@ Extensions
411411
412412Session
413413
414- You will need to use :func:`apsw.aio.make_session`
414+ You should use :func: `apsw.aio.make_session ` to make a
415+ :class: `Session ` in async mode.
415416
416417:class: `apsw.fts5.Table `
417418
418419 Virtually every method and property needs to access the database.
419- Therefore you will need to run all of them in the database thread
420+ Therefore you should run all of them in the database thread
420421 using :meth: `Connection.async_run `
421422
422423 .. code-block :: python
@@ -497,9 +498,7 @@ CpuTotal / CpuEvtLoop / CpuDbWorker
497498 database worker thread.
498499
499500The results show that what is used only matters if you are doing very
500- large numbers of calls because of very small row batch sizes. APSW
501- has to allocate space for results, so increasing the prefetch size
502- results in more memory consumption and more CPU time to allocate it.
501+ large numbers of calls because of very small row batch sizes.
503502
504503.. csv-table :: Benchmark Results
505504 :widths: auto
0 commit comments