without-durability-sqlite 0.0.2__tar.gz → 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {without_durability_sqlite-0.0.2 → without_durability_sqlite-0.0.4}/PKG-INFO +6 -3
- {without_durability_sqlite-0.0.2 → without_durability_sqlite-0.0.4}/README.md +4 -1
- {without_durability_sqlite-0.0.2 → without_durability_sqlite-0.0.4}/pyproject.toml +3 -3
- {without_durability_sqlite-0.0.2 → without_durability_sqlite-0.0.4}/pyproject.toml.orig +3 -3
- {without_durability_sqlite-0.0.2 → without_durability_sqlite-0.0.4}/src/without_durability_sqlite/store.py +29 -0
- {without_durability_sqlite-0.0.2 → without_durability_sqlite-0.0.4}/src/without_durability_sqlite/__init__.py +0 -0
- {without_durability_sqlite-0.0.2 → without_durability_sqlite-0.0.4}/src/without_durability_sqlite/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: without-durability-sqlite
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A without-durability checkpoint store and queue backed by one SQLite file, with no server and no third-party driver.
|
|
5
5
|
Author: Josh Karpel
|
|
6
6
|
Author-email: Josh Karpel <josh.karpel@gmail.com>
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.14
|
|
15
15
|
Classifier: Topic :: Software Development :: Libraries
|
|
16
16
|
Classifier: Typing :: Typed
|
|
17
|
-
Requires-Dist: without-durability==0.0.
|
|
17
|
+
Requires-Dist: without-durability==0.0.4
|
|
18
18
|
Requires-Python: >=3.14
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
|
|
@@ -30,6 +30,8 @@ from without_durability_sqlite import SqliteCheckpointer, SqliteDurable, SqliteS
|
|
|
30
30
|
database = connect("workflows.db")
|
|
31
31
|
await migrate(database)
|
|
32
32
|
durable = SqliteDurable(SqliteCheckpointer(database), SqliteScheduler(database))
|
|
33
|
+
...
|
|
34
|
+
await database.aclose()
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
It is the smallest thing that still meets every requirement the interface states,
|
|
@@ -56,4 +58,5 @@ See the
|
|
|
56
58
|
(with the [API reference](https://without.help/without-durability-sqlite/reference/))
|
|
57
59
|
for the statements, why `connect` pays the fsync that the usual WAL advice trades
|
|
58
60
|
away, why an effect here is a synchronous callback where the Postgres store's is
|
|
59
|
-
`async`,
|
|
61
|
+
`async`, how the blocking driver is kept off the event loop, and why closing goes
|
|
62
|
+
through `aclose` rather than the connection.
|
|
@@ -10,6 +10,8 @@ from without_durability_sqlite import SqliteCheckpointer, SqliteDurable, SqliteS
|
|
|
10
10
|
database = connect("workflows.db")
|
|
11
11
|
await migrate(database)
|
|
12
12
|
durable = SqliteDurable(SqliteCheckpointer(database), SqliteScheduler(database))
|
|
13
|
+
...
|
|
14
|
+
await database.aclose()
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
It is the smallest thing that still meets every requirement the interface states,
|
|
@@ -36,4 +38,5 @@ See the
|
|
|
36
38
|
(with the [API reference](https://without.help/without-durability-sqlite/reference/))
|
|
37
39
|
for the statements, why `connect` pays the fsync that the usual WAL advice trades
|
|
38
40
|
away, why an effect here is a synchronous callback where the Postgres store's is
|
|
39
|
-
`async`,
|
|
41
|
+
`async`, how the blocking driver is kept off the event loop, and why closing goes
|
|
42
|
+
through `aclose` rather than the connection.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["uv_build>=0.
|
|
2
|
+
requires = ["uv_build>=0.12.3,<0.13"]
|
|
3
3
|
build-backend = "uv_build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "without-durability-sqlite"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.4"
|
|
8
8
|
description = "A without-durability checkpoint store and queue backed by one SQLite file, with no server and no third-party driver."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Topic :: Software Development :: Libraries",
|
|
21
21
|
"Typing :: Typed",
|
|
22
22
|
]
|
|
23
|
-
dependencies = ["without-durability==0.0.
|
|
23
|
+
dependencies = ["without-durability==0.0.4"]
|
|
24
24
|
|
|
25
25
|
[[project.authors]]
|
|
26
26
|
name = "Josh Karpel"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["uv_build>=0.
|
|
2
|
+
requires = ["uv_build>=0.12.3,<0.13"]
|
|
3
3
|
build-backend = "uv_build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "without-durability-sqlite"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.4"
|
|
8
8
|
description = "A without-durability checkpoint store and queue backed by one SQLite file, with no server and no third-party driver."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -24,7 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Typing :: Typed",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
-
"without-durability==0.0.
|
|
27
|
+
"without-durability==0.0.4",
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[tool.uv.sources]
|
|
@@ -236,6 +236,7 @@ class Database:
|
|
|
236
236
|
Build it with `connect`, which applies the pragmas that make this durable rather than
|
|
237
237
|
merely persistent. Share one between the checkpoint store and the queue: that is what
|
|
238
238
|
makes `SqliteDurable.arrive` a single commit, and it is checked rather than assumed.
|
|
239
|
+
Close it with `aclose`, never `connection.close()`, for the reason given there.
|
|
239
240
|
"""
|
|
240
241
|
|
|
241
242
|
connection: sqlite3.Connection
|
|
@@ -272,6 +273,34 @@ class Database:
|
|
|
272
273
|
running.add_done_callback(lambda _finished: self.guard.release())
|
|
273
274
|
return await asyncio.shield(running)
|
|
274
275
|
|
|
276
|
+
async def aclose(self) -> None:
|
|
277
|
+
"""
|
|
278
|
+
Close the connection once nobody is inside it.
|
|
279
|
+
|
|
280
|
+
The other half of `run`'s handshake, and the reason a caller must never reach
|
|
281
|
+
for `connection.close()` itself. `sqlite3.close()` frees the connection and
|
|
282
|
+
finalizes its statements; a thread still executing one is then reading freed
|
|
283
|
+
memory, which segfaults the process rather than raising. `run` makes that
|
|
284
|
+
reachable by design, since a cancelled caller unwinds while its thread runs on,
|
|
285
|
+
so a shutdown that follows a cancellation is exactly when the two meet: the
|
|
286
|
+
worker's task is cancelled, the statement it left behind is still in flight, and
|
|
287
|
+
the close lands on top of it.
|
|
288
|
+
|
|
289
|
+
Taking the guard is what waits that out, because `run` releases it from the
|
|
290
|
+
thread rather than from its caller. That wait is unbounded: an effect hung
|
|
291
|
+
inside a `run` thread holds the guard until it returns, and cancelling this
|
|
292
|
+
coroutine while it is parked on the guard abandons the close, leaving the
|
|
293
|
+
connection open. The guard is then let go again, so a `run` arriving after
|
|
294
|
+
this fails on a closed connection, which is the loud version of a bug that
|
|
295
|
+
would otherwise be silent.
|
|
296
|
+
|
|
297
|
+
The close itself goes to a thread like every other driver call: under WAL it
|
|
298
|
+
runs the final checkpoint (and, with `synchronous=FULL`, an fsync), which is
|
|
299
|
+
real disk I/O that does not belong on the event loop.
|
|
300
|
+
"""
|
|
301
|
+
async with self.guard:
|
|
302
|
+
await asyncio.to_thread(self.connection.close)
|
|
303
|
+
|
|
275
304
|
|
|
276
305
|
def connect(path: Path | str, *, timeout: timedelta = timedelta(seconds=5)) -> Database:
|
|
277
306
|
"""
|
|
File without changes
|
|
File without changes
|