Skip to content

feat(core): replace rusqlite with turso 0.6.0-pre.26 (multi-process WAL)#35545

Draft
FrozenPandaz wants to merge 4 commits intomasterfrom
turso-rebase
Draft

feat(core): replace rusqlite with turso 0.6.0-pre.26 (multi-process WAL)#35545
FrozenPandaz wants to merge 4 commits intomasterfrom
turso-rebase

Conversation

@FrozenPandaz
Copy link
Copy Markdown
Contributor

@FrozenPandaz FrozenPandaz commented May 2, 2026

Current Behavior

Nx uses rusqlite (C SQLite bindings) with a hand-rolled retry/backoff/lock-file layer to handle SQLite concurrency across the daemon, CLI, and plugin workers. Rusqlite bundles C SQLite, which blocks WASM compilation and requires a C compiler for every target platform.

Expected Behavior

Replace rusqlite with the published turso crate (0.6.0-pre.26) — a pure-Rust SQLite rewrite. Primary motivation: WASM support. Turso has no C dependencies, so it can compile to WASM natively.

Secondary benefits:

  • ~960 lines of workarounds removed (retry macro, lock files, WAL negotiation, WSL1 detection, filesystem diagnostics)
  • Simpler API — no lifetime-laden Statement types leaking through the codebase

This rebases the closed spike #34933, which was closed because turso couldn't share a DB file across daemon + CLI + workers. Upstream tursodatabase/turso#6236 (merged 2026-04-20) added the multi-process tshm WAL coordination protocol, and 0.6.0-pre.26 ships it.

CI status: green

Run Failures Lock errors
First attempt (no flag) 130+ 100+ on .db
experimental_multiprocess_wal(true) only 100+ 100+ on .db-wal
+ vendored SDK patch 0 0

The full nx affected --targets=lint,test,build,e2e,e2e-ci,... pipeline passes (42m 27s, 250 e2e tasks ✔), with no "Locking error" / "Failed to open database" / "Corrupt coordination" anywhere in the logs.

Why a vendored patch (and how to remove it)

turso 0.6.0-pre.26 exposes the multi-process WAL feature via Builder::experimental_multiprocess_wal(true), but turso_sdk_kit-0.6.0-pre.26 (a transitive dep that wires it up) has a one-line bug. In src/rsapi.rs:

// Line 673-676: correctly compute open_flags with NoLock
let mut open_flags = OpenFlags::default();
if opts.enable_multiprocess_wal {
    open_flags |= OpenFlags::NoLock;
}
let file = io.open_file(&self.config.path, open_flags, true)?;  // .db opens correctly

// Line 703-712: but then hardcodes default flags for the inner open
Database::open_with_flags_async(
    &mut state.open_db_state,
    io.clone(),
    &self.config.path,
    db_file,
    OpenFlags::default(),  // <-- bug: should be open_flags
    opts,
    ...
)?

The result: the .db file opens with NoLock correctly, but the .db-wal file (opened inside Database::open_with_flags_async) takes an exclusive lock anyway — defeating the multi-process feature. We see the lock just move from .db to .db-wal when the flag is enabled but the patch isn't.

Workaround (this PR): vendor turso_sdk_kit under cargo-patches/turso_sdk_kit/ and reference it from the workspace Cargo.toml via [patch.crates-io]. The only delta vs the registry source is that one block in src/rsapi.rs (search "PATCH:" in the file). 256K, 10 files.

Removal: an upstream issue/PR with the diagnosis is being filed. Once turso publishes a release with the fix, drop cargo-patches/, drop the [patch.crates-io] section, and bump the turso version. Tracked in the inline comment on Cargo.toml.

Files changed

File Change
Cargo.toml rusqlite 0.32.1turso 0.6.0-pre.26 (no C deps, WASM-ready); [patch.crates-io] for the SDK fix
cargo-patches/turso_sdk_kit/ Vendored copy of turso_sdk_kit-0.6.0-pre.26 with one-line patch in src/rsapi.rs
db/initialize.rs ~500 → ~120 lines. Builder::new_local(...).experimental_multiprocess_wal(true).build() → WAL pragma → busy_timeout → create tables. Drops the WSL1 / journal-mode / filesystem-diagnostics machinery
db/connection.rs turso::Connection wrapper with DbValue/DbRow shims
db/mod.rs Drops the lock-file dance; turso's busy_timeout + WAL handle multi-process directly. Keeps master's filename-based schema versioning (DB_VERSION const + cleanup_stale_db_files)
cache/cache.rs DbValue params; fetch_cache_rows ported from rarray + query_map to a dynamic IN (?,?,...) clause
tasks/task_history.rs rarray()IN (?,?,?), DbValue params
tasks/details.rs begin/commit_transaction(), DbValue params
tasks/running_tasks_service.rs DbValue params
telemetry/{mod,service}.rs DbValue params, DbRow results
.prettierignore Skip cargo-patches/ (vendored upstream code)

Known caveats

  • turso is still in pre-release (0.6.0-pre.26). We're now exercising it through the entire e2e suite with the patch applied, but it's worth flagging when reviewing the rollout.
  • Local stress test of two-process opens passed 10/10. There's a rare upstream tshm initialization race ("Corrupt: shared WAL coordination file is smaller than the coordination header") that's hit ~1 in dozens of runs in tight bursts; CI has not surfaced it but we should add open-time retry in a follow-up if it appears.

Related Issue(s)

Supersedes #34933 (the original spike).

Fixes #

Replaces the rusqlite C-bindings backend with the pure-Rust turso crate
(v0.6.0-pre.26), which now ships process-shared WAL coordination via the
upstream tshm protocol (tursodatabase/turso#6236, merged 2026-04-20).

The previous spike (#34933) closed because turso could not share a DB
file across the daemon, CLI, and plugin workers. The new release fixes
that, so the spike can land using a published crate version instead of
a git-branch dependency.

Removes the rusqlite-era retry/lock-file/WAL-negotiation/WSL1 detection
machinery that existed solely to work around rusqlite concurrency edge
cases; turso's busy_timeout + WAL handle the same cases natively.

Cargo.toml: rusqlite 0.32.1 -> turso 0.6.0-pre.26 (no C deps, WASM-ready)
db/initialize.rs: ~500 -> ~120 lines; open + WAL pragma + create tables
db/connection.rs: turso::Connection wrapper with DbValue/DbRow shims
db/mod.rs: drop lock-file dance; keep DB_VERSION filename + cleanup
cache/cache.rs: port fetch_cache_rows from rarray to dynamic IN clause
tasks/task_history.rs, details.rs, running_tasks_service.rs: DbValue params
telemetry/mod.rs, service.rs: DbValue params, DbRow results

Adds initialize_db_concurrent_connections test verifying two connections
can simultaneously open and read/write the same DB file.
@netlify
Copy link
Copy Markdown

netlify Bot commented May 2, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 22afaea
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69f750f434719d0008edbe32
😎 Deploy Preview https://deploy-preview-35545--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 2, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 22afaea
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69f750f4354a8e0007a510d4
😎 Deploy Preview https://deploy-preview-35545--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented May 2, 2026

View your CI Pipeline Execution ↗ for commit 22afaea

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 42m 27s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 17s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 22s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-03 14:29:12 UTC

Without this flag turso 0.6.0-pre.26 silently uses an in-process WAL
backend, so a second OS process opening the same DB hits
"File is locked by another process" — the same failure mode that
killed the original spike (#34933).

Note: with the flag set, the lock contention moves from .db to
.db-wal but still rejects a second process in real daemon+CLI flows
(verified via local two-process repro). Pushing anyway so CI can
exercise the new code path on Linux x64 and we can see if the
behavior is platform-specific or universally broken.
Previous run was canceled before e2e tests started, so we don't know
whether experimental_multiprocess_wal(true) actually changed Linux
behavior. Empty commit to re-fire the pipeline.
Confirmed via local two-process repro that the patch unlocks
multi-process DB opens. Upstream pre.26 has a one-line bug in
turso_sdk_kit/src/rsapi.rs: it computes open_flags with NoLock
when experimental_multiprocess_wal is enabled, applies it to the
initial .db file open, but then hardcodes OpenFlags::default() on
the inner Database::open_with_flags_async call — which is what
opens the .db-wal file with an exclusive lock that blocks every
other process.

Vendored copy of turso_sdk_kit-0.6.0-pre.26 lives under
cargo-patches/, referenced via [patch.crates-io] in Cargo.toml.
The only delta vs the registry source is in src/rsapi.rs around
the Opening phase — search for "PATCH:" to find it.

Drop this once upstream cuts a release with the fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant