Refactor entire codebase and rewrite documentation
This commit is contained in:
94
Sources/DataLiteCore/Docs.docc/Articles/Multithreading.md
Normal file
94
Sources/DataLiteCore/Docs.docc/Articles/Multithreading.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Multithreading Strategies
|
||||
|
||||
Coordinate SQLite safely across queues, actors, and Swift concurrency using DataLiteCore.
|
||||
|
||||
SQLite remains fundamentally serialized, so deliberate connection ownership and scheduling are
|
||||
essential for correctness and performance. DataLiteCore does not include a built-in connection
|
||||
pool, but its deterministic behavior and configuration options allow you to design synchronization
|
||||
strategies that match your workload.
|
||||
|
||||
## Core Guidelines
|
||||
|
||||
- **One connection per queue or actor**. Keep each ``Connection`` confined to a dedicated serial
|
||||
`DispatchQueue` or an `actor` to ensure ordered execution and predictable statement lifecycles.
|
||||
- **Do not share statements across threads**. ``Statement`` instances are bound to their parent
|
||||
``Connection`` and are not thread-safe.
|
||||
- **Scale with multiple connections**. For concurrent workloads, use a dedicated writer connection
|
||||
alongside a pool of readers so long-running transactions don’t block unrelated operations.
|
||||
|
||||
```swift
|
||||
actor Database {
|
||||
private let connection: Connection
|
||||
|
||||
init(path: String) throws {
|
||||
connection = try Connection(
|
||||
path: path,
|
||||
options: [.readwrite, .create, .fullmutex]
|
||||
)
|
||||
connection.busyTimeout = 5_000 // wait up to 5 seconds for locks
|
||||
}
|
||||
|
||||
func insertUser(name: String) throws {
|
||||
let statement = try connection.prepare(
|
||||
sql: "INSERT INTO users(name) VALUES (?)"
|
||||
)
|
||||
try statement.bind(name, at: 1)
|
||||
try statement.step()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Encapsulating database work in an `actor` or serial queue aligns naturally with Swift Concurrency
|
||||
while maintaining safe access to SQLite’s synchronous API.
|
||||
|
||||
## Synchronization Options
|
||||
|
||||
- ``Connection/Options/nomutex`` — disables SQLite’s internal mutexes (multi-thread mode). Each
|
||||
connection must be accessed by only one thread at a time.
|
||||
|
||||
```swift
|
||||
let connection = try Connection(
|
||||
location: .file(path: "/path/to/sqlite.db"),
|
||||
options: [.readwrite, .nomutex]
|
||||
)
|
||||
```
|
||||
|
||||
- ``Connection/Options/fullmutex`` — enables serialized mode with full internal locking. A single
|
||||
``Connection`` may be shared across threads, but global locks reduce throughput.
|
||||
|
||||
```swift
|
||||
let connection = try Connection(
|
||||
location: .file(path: "/path/to/sqlite.db"),
|
||||
options: [.readwrite, .fullmutex]
|
||||
)
|
||||
```
|
||||
|
||||
SQLite defaults to serialized mode, but concurrent writers still contend for locks. Plan long
|
||||
transactions carefully and adjust ``ConnectionProtocol/busyTimeout`` to handle `SQLITE_BUSY`
|
||||
conditions gracefully.
|
||||
|
||||
- SeeAlso: [Using SQLite In Multi-Threaded Applications](https://sqlite.org/threadsafe.html)
|
||||
|
||||
## Delegates and Side Effects
|
||||
|
||||
``ConnectionDelegate`` and ``ConnectionTraceDelegate`` callbacks execute synchronously on SQLite’s
|
||||
internal thread. Keep them lightweight and non-blocking. Offload work to another queue when
|
||||
necessary to prevent deadlocks or extended lock holds.
|
||||
|
||||
```swift
|
||||
final class Logger: ConnectionTraceDelegate {
|
||||
private let queue = DispatchQueue(label: "logging")
|
||||
|
||||
func connection(
|
||||
_ connection: ConnectionProtocol,
|
||||
trace sql: ConnectionTraceDelegate.Trace
|
||||
) {
|
||||
queue.async {
|
||||
print("SQL:", sql.expandedSQL)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This pattern keeps tracing responsive and prevents SQLite’s internal thread from being blocked by
|
||||
slow I/O or external operations.
|
||||
Reference in New Issue
Block a user