Skip to content

Commit 4e1ae75

Browse files
committed
Doc review
1 parent aaef485 commit 4e1ae75

3 files changed

Lines changed: 66 additions & 64 deletions

File tree

apsw/aio.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,31 +37,34 @@
3737
# framework as documented below.
3838
3939
with apsw.aio.contextvar_set(apsw.aio.deadline,
40-
anyio.current_time() + 10):
40+
anyio.current_time() + 10):
4141
42-
async for row in await db.execute("SELECT time_consuming ..."):
43-
print(f"{row=}")
42+
async for row in await db.execute("time consuming query ..."):
43+
print(f"{row=}")
4444
4545
4646
:class:`AsyncIO`
4747
48-
This is the only way to set a deadline. :exc:`TimeoutError` will
48+
:code:`apsw.aio.deadline` is the only way to set a deadline.
49+
:exc:`TimeoutError` will
4950
be raised if the deadline is exceeded. The current time is
5051
available from :meth:`asyncio.get_running_loop().time()
5152
<asyncio.loop.time>`
5253
5354
:class:`Trio`
5455
55-
If this is set then it is used for the deadline. :exc:`trio.TooSlowError`
56-
is raised. The current time is available from :func:`trio.current_time`.
56+
If :code:`apsw.aio.deadline` is set then it is used for the
57+
deadline. :exc:`trio.TooSlowError` is raised. The current time
58+
is available from :func:`trio.current_time`.
5759
5860
Otherwise the :func:`trio.current_effective_deadline` where the
5961
call is made is used.
6062
6163
AnyIO
6264
63-
If this is set then it is used for the deadline. :exc:`TimeoutError` is raised.
64-
The current time is available from :func:`anyio.current_time`.
65+
If :code:`apsw.aio.deadline` is set then it is used for the
66+
deadline. :exc:`TimeoutError` is raised. The current time is
67+
available from :func:`anyio.current_time`.
6568
6669
Otherwise the :func:`anyio.current_effective_deadline` where the
6770
call is made is used.
@@ -71,7 +74,7 @@
7174
check_progress_steps: contextvars.ContextVar[int] = contextvars.ContextVar(
7275
"apsw.aio.check_progress_steps", default=50_000
7376
)
74-
"""How many steps between checks to check for cancellation and deadlines
77+
"""How many internal SQLite steps between checks for cancellation and deadlines
7578
7679
While SQLite queries are executing, periodic checks are made to see if
7780
the request has been cancelled, or the deadline exceeded. This is
@@ -94,9 +97,9 @@
9497
if sys.version_info >= (3, 14):
9598

9699
def contextvar_set(var: contextvars.ContextVar[T], value: T) -> contextvars.Token[T]:
97-
"""wrapper for setting a contextvar during a with block
100+
"""Wrapper for setting a :class:`~contextvars.ContextVar` during a :code:`with` block
98101
99-
Python 3.14 lets you do::
102+
Python 3.14+ lets you do::
100103
101104
with var.set(value):
102105
# code here
@@ -105,7 +108,7 @@ def contextvar_set(var: contextvars.ContextVar[T], value: T) -> contextvars.Toke
105108
This wrapper provides the same functionality for all
106109
Python versions::
107110
108-
with contextvar_set(var, value):
111+
with apsw.aio.contextvar_set(var, value):
109112
# code here
110113
...
111114
@@ -238,7 +241,7 @@ async def _coro_for_stopasynciteration():
238241

239242

240243
class AsyncIO:
241-
"""Uses :mod:`asyncio` for async concurrency"""
244+
""":class:`Controller <apsw.AsyncConnectionController>` for :mod:`asyncio`"""
242245

243246
def configure(self, db: apsw.Connection):
244247
"Setup database, just after it is created"
@@ -323,7 +326,7 @@ def async_run_coro(self, coro: Coroutine):
323326
if sys.version_info < (3, 11):
324327

325328
async def run_coro_in_loop(self, coro: Coroutine, tracker: _CallTracker, context: contextvars.Context) -> Any:
326-
"executes the coro in the event loop"
329+
"Executes the coro in the event loop"
327330

328331
task = context.run(asyncio.create_task, coro)
329332
tracker.cancel_async_cb = task.cancel
@@ -337,7 +340,7 @@ async def run_coro_in_loop(self, coro: Coroutine, tracker: _CallTracker, context
337340
elif sys.version_info < (3, 12):
338341

339342
async def run_coro_in_loop(self, coro: Coroutine, tracker: _CallTracker, context: contextvars.Context) -> Any:
340-
"executes the coro in the event loop"
343+
"Executes the coro in the event loop"
341344

342345
task = context.run(asyncio.create_task, coro)
343346
tracker.cancel_async_cb = task.cancel
@@ -350,7 +353,7 @@ async def run_coro_in_loop(self, coro: Coroutine, tracker: _CallTracker, context
350353
else:
351354

352355
async def run_coro_in_loop(self, coro: Coroutine, tracker: _CallTracker, context: contextvars.Context) -> Any:
353-
"executes the coro in the event loop"
356+
"Executes the coro in the event loop"
354357

355358
# Note: we don't set cancel_async_cb back to None on exit
356359
# because cancelling an already completed task is doesn't
@@ -374,7 +377,7 @@ def __init__(self, *, thread_name: str = "asyncio apsw background worker"):
374377

375378

376379
class Trio:
377-
"""Uses |trio| for async concurrency"""
380+
""":class:`Controller <apsw.AsyncConnectionController>` for |trio|"""
378381

379382
def configure(self, db: apsw.Connection):
380383
"Setup database, just after it is created"
@@ -450,7 +453,7 @@ def async_run_coro(self, coro: Coroutine):
450453
coro.close()
451454

452455
async def run_coro_in_loop(self, coro: Coroutine, tracker: _CallTracker):
453-
"executes the coro in the event loop"
456+
"Executes the coro in the event loop"
454457
with trio.fail_at(deadline=math.inf if tracker.deadline_loop is None else tracker.deadline_loop) as scope:
455458
tracker.cancel_async_cb = scope.cancel
456459
if tracker.is_cancelled:
@@ -467,7 +470,7 @@ def __init__(self, *, thread_name: str = "trio apsw background worker"):
467470

468471

469472
class AnyIO:
470-
"""Uses |anyio| for async concurrency"""
473+
""":class:`Controller <apsw.AsyncConnectionController>` for |anyio|"""
471474

472475
def configure(self, db: apsw.Connection):
473476
"Setup database, just after it is created"
@@ -547,7 +550,7 @@ def async_run_coro(self, coro: Coroutine):
547550
coro.close()
548551

549552
async def run_coro_in_loop(self, coro: Coroutine, tracker: _CallTracker):
550-
"executes coro in the event loop"
553+
"Executes coro in the event loop"
551554

552555
with anyio.fail_after(
553556
math.inf if tracker.deadline_loop is None else tracker.deadline_loop - anyio.current_time()

doc/async.rst

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ Concurrency & Async
66
How 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

1517
This means you cannot get more concurrency per connection by using
1618
additional 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

2022
SQLite is inherently synchronous due to being written in C and using
2123
the 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

3840
Free 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

4851
Async
4952

@@ -58,6 +61,10 @@ Async
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+
6168
How 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
128135
However 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
249256
Close
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
276276
Callbacks
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

398398
Although it seems like a lot, they are around 50 lines of code, and
@@ -411,12 +411,13 @@ Extensions
411411

412412
Session
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

499500
The 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

src/apswtypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def configure(self, connection: Connection):
201201
...
202202

203203
async def send(self, call: Callable[[], Any]) -> Any:
204-
"""Called from outside the worker thread to send to worker thread
204+
"""Called from outside the worker thread to send call to worker thread
205205
206206
This should be async or return an awaitable, and forward
207207
``call`` to the worker thread where it is called with no

0 commit comments

Comments
 (0)