Skip to content

fix: worker thread can silently write to the real client db when a test only overrides .conn on the main thread - #56

Draft
JarbasAl wants to merge 2 commits into
devfrom
fix/f11-conn-setter-thread-safety
Draft

fix: worker thread can silently write to the real client db when a test only overrides .conn on the main thread#56
JarbasAl wants to merge 2 commits into
devfrom
fix/f11-conn-setter-thread-safety

Conversation

@JarbasAl

@JarbasAl JarbasAl commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

conn.setter (hivemind_sqlite_database/__init__.py) only binds the calling thread's connection — the docstring said as much. A test that does db.conn = sqlite3.connect(":memory:") and then exercises a worker thread had that thread's .conn getter fall through to _connect(), which opens self._db_path — the real xdg client database (xdg_data_home()/hivemind-core/<name>.db) if the instance was constructed normally. No error, no warning, just a real file silently written to.

Fix

Added a db_path constructor field. When set (e.g. db_path=":memory:"), it overrides the computed xdg path entirely, so every thread — including ones that haven't opened a connection yet — agrees on the same, safe target. This closes the gap for future threads, which a setter-side guard could not: a guard at assignment time can only check threads that already hold a connection, not ones that connect later.

Kept the .conn setter (many existing single-threaded tests rely on it, e.g. via object.__new__(SQLiteDB) bypassing __post_init__ entirely, where _db_path is never even set), but rewrote its docstring to state the hazard plainly and point callers at db_path instead of presenting the setter as the sanctioned way to inject a test database.

Test plan

  • pytest tests -q -p no:ovoscope -p no:recording --timeout=600 — 66 passed, 3 skipped
  • added test_db_path_override_keeps_worker_threads_off_disk: injects db_path=":memory:", exercises a second thread, asserts the real db file under a mocked xdg home is never created

…real client db

The conn.setter only ever bound the calling thread's connection; any
other thread that later touched .conn opened its own connection
against self._db_path, which defaulted to the real xdg client
database. A test that injects an in-memory db and then exercises a
worker thread had that thread silently fall back to writing the real
file, no error raised.

Add a db_path constructor field that overrides the computed xdg path
for every thread, present and future, so tests (and any other caller
wanting an isolated database) have one target every thread agrees on.
Keep the .conn setter for existing single-threaded call sites, but
document the hazard explicitly and point at db_path instead of
presenting the setter as the sanctioned way to inject a test db.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f1b3d714-66fc-467b-a8d1-51f16b439198

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the fix label Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Beep boop! Data processing finished. ⚙️

I've aggregated the results of the automated checks for this PR below.

📊 Coverage

The coverage audit is ready for your inspection. 📋

85.6% total coverage

Per-file coverage (2 files)
File Coverage Missing lines
hivemind_sqlite_database/version.py 0.0% 5
hivemind_sqlite_database/__init__.py 87.8% 24

Full report: download the coverage-report artifact.

🔍 Lint

Processing complete! Details follow. 📬

ruff: issues found — see job log

📋 Repo Health

Checking if the repo is following its diet. 🥗

✅ All required files present.

Latest Version: 0.4.2a1

hivemind_sqlite_database/version.py — Version file
README.md — README
LICENSE.md — License file (consider renaming to LICENSE)
pyproject.toml — pyproject.toml
⚠️ setup.py — setup.py
CHANGELOG.md — Changelog
hivemind_sqlite_database/version.py has valid version block markers

🏷️ Release Preview

Ensuring the version bump is correctly calculated. 🔢

Current: 0.4.2a1Next: 0.4.3a1

Signal Value
Label fix
PR title fix: worker thread can silently write to the real client db when a test only overrides .conn on the main thread
Bump build

✅ PR title follows conventional commit format.


🚀 Release Channel Compatibility

Predicted next version: 0.4.3a1

Channel Status Note Current Constraint
Stable Not in channel -
Testing Not in channel -
Alpha Compatible hivemind-sqlite-database>=0.4.0a7

🔒 Security (pip-audit)

Our digital defenses have been updated. 🛡️

✅ No known vulnerabilities found (70 packages scanned).

🔨 Build Tests

From source to binary, let's see how it holds up. 🧱

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

⚖️ License Check

Checking if we're following open-source best practices. 📏

✅ No license violations found.

Policy: Apache 2.0 (universal donor). StrongCopyleft / NetworkCopyleft / WeakCopyleft / Other / Error categories fail. MPL allowed.


Every line of code matters. Thanks for contributing! 💖

…hreads

A plain ":memory:" database belongs to the connection that opened it, so
every thread opened a second, empty one. Schema creation runs once, on
the thread that built the object, so a worker thread found no tables and
counted zero clients. The test could not see it: "SELECT 1" answers just
as well against an empty database.

Map ":memory:" to a named shared cache database, one name per instance so
two in-memory databases in the same process stay independent, and hold one
connection open for the lifetime of the object because a shared in-memory
database disappears with its last connection.

Also create the parent directory for an explicit db_path again. It only
happened on the xdg branch, so SQLiteDB(db_path="/var/lib/newdir/x.db")
failed with "unable to open database file".

The thread test now writes a client on one thread and reads it on another.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant