sqlite: document lifecycle and concurrency behaviour
User asked what happens if the connection is never closed and whether concurrent calls are safe. Adds a Lifecycle and concurrency section to README.md covering: - not closing only leaks fds; data is durable on commit - 3-tier concurrency model: same service / multi-CLI same service / multi-process, with the actual busy_timeout default (5s) and what to do if a longer wait is needed (run PRAGMA busy_timeout via sqlite_exec) - :memory: semantics (gone on close, even within one process) - NFS/SMB caveat No code changes; current defaults (busy_timeout 5s, synchronous FULL, locking normal) are sensible and the existing test sweep already verified end-to-end.
This commit is contained in:
@@ -267,3 +267,60 @@ client logs; plugin loading shows up in the service log at
|
||||
- The plugin keeps connections in memory. They are closed on OpenCode
|
||||
shutdown. The cleanup hook also runs on plugin reload, so editing the
|
||||
file in place does not leak file descriptors.
|
||||
|
||||
## Lifecycle and concurrency
|
||||
|
||||
**What happens if I never call `sqlite_close`?**
|
||||
|
||||
Data is safe. `better-sqlite3` auto-commits every transaction and the
|
||||
default `synchronous=FULL` setting fsyncs on commit, so a successful
|
||||
`sqlite_exec` return means the bytes are on disk. The only cost is a
|
||||
leaked file descriptor: each open connection holds one fd on the
|
||||
database file (plus, in WAL mode, one each on the `-wal` and `-shm`
|
||||
sidecar files). Connections are released when:
|
||||
|
||||
- the model calls `sqlite_close`,
|
||||
- the plugin is reloaded (edit `index.ts`, save),
|
||||
- the OpenCode service is restarted.
|
||||
|
||||
For a one-off task it is fine to skip `sqlite_close` and let the next
|
||||
service restart clean up. For a long-lived service that opens many
|
||||
aliases, call `sqlite_close` to keep the fd count predictable.
|
||||
|
||||
**`":memory:"` databases** are gone the moment the last handle closes,
|
||||
even within the same process. The two `sqlite_open(":memory:")` calls
|
||||
above are *not* the same database.
|
||||
|
||||
**Can I call the tools concurrently?**
|
||||
|
||||
There are three levels to think about:
|
||||
|
||||
1. *Within one OpenCode service process* — Node is single-threaded and
|
||||
`better-sqlite3` is a synchronous API, so even if the model fires
|
||||
several tool calls in parallel, the service processes them one at a
|
||||
time. The database itself never sees a real concurrent call from
|
||||
here, so you do not need locks.
|
||||
|
||||
2. *Multiple OpenCode CLI / TUI sessions against the same service* —
|
||||
the service is a single process, so the same single-threaded rule
|
||||
applies.
|
||||
|
||||
3. *Multiple independent OpenCode services (or any other process)
|
||||
opening the same database file* — this is real concurrency.
|
||||
`better-sqlite3` opens the file with `locking_mode=normal` and the
|
||||
default `busy_timeout=5000ms`, so the second writer politely waits
|
||||
up to 5 seconds for the first writer's transaction to commit. If
|
||||
the wait expires, the call returns `SQLITE_BUSY` and the model
|
||||
should retry. There is no built-in knob on this plugin to raise
|
||||
the timeout; if you need a longer wait, run
|
||||
`PRAGMA busy_timeout = 30000;` once after `sqlite_open` (use
|
||||
`sqlite_exec`).
|
||||
|
||||
4. *Network filesystems (NFS, SMB, sshfs)* — SQLite's locking is
|
||||
unreliable on them and `SQLITE_BUSY` is the best case. Copy the
|
||||
database to local disk first, or run a real database server.
|
||||
|
||||
**Read-only mode (`mode: "ro"`)** opens the file normally and rejects
|
||||
writes inside the plugin. The journal mode is left at the default
|
||||
(`delete` for files, `memory` for `:memory:`), so concurrent reads
|
||||
work as expected.
|
||||
|
||||
Reference in New Issue
Block a user