Merge 'Make Rust bindings actually async' from Pedro Muniz

This PR introduces a `Context` object that is stored in the `Completion`
that currently only stores a `Waker`. In the future, I want to add some
sort of abort signal so that we can abort tasks that share the same
Context. To pass the Waker, I introduced a `step_with_waker` function in
`Statement` that delegates to an internal `_step` function. `_step` is
the previous `step` but just with the `Option<&Waker>` argument.
I was going to try and have the BusyHandler by truly async as well, but
I decided to not do it here, because it will be slightly complicated to
achieve.

Closes #3535
This commit is contained in:
Pekka Enberg
2025-10-15 19:38:24 +03:00
committed by GitHub
18 changed files with 736 additions and 111 deletions

484
Cargo.lock generated
View File

@@ -255,18 +255,104 @@ dependencies = [
"wait-timeout",
]
[[package]]
name = "async-stream"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
dependencies = [
"async-stream-impl",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-stream-impl"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "atomic"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "axum"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper",
"tower 0.5.2",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
]
[[package]]
name = "backtrace"
version = "0.3.74"
@@ -291,6 +377,12 @@ dependencies = [
"backtrace",
]
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
@@ -639,6 +731,45 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "console-api"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857"
dependencies = [
"futures-core",
"prost 0.13.5",
"prost-types",
"tonic",
"tracing-core",
]
[[package]]
name = "console-subscriber"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01"
dependencies = [
"console-api",
"crossbeam-channel",
"crossbeam-utils",
"futures-task",
"hdrhistogram",
"humantime",
"hyper-util",
"prost 0.13.5",
"prost-types",
"serde",
"serde_json",
"thread_local",
"tokio",
"tokio-stream",
"tonic",
"tracing",
"tracing-core",
"tracing-subscriber",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
@@ -1666,6 +1797,25 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "h2"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap 2.11.1",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "half"
version = "2.5.0"
@@ -1700,6 +1850,19 @@ dependencies = [
"hashbrown 0.15.2",
]
[[package]]
name = "hdrhistogram"
version = "7.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
dependencies = [
"base64 0.21.7",
"byteorder",
"flate2",
"nom",
"num-traits",
]
[[package]]
name = "heck"
version = "0.5.0"
@@ -1744,6 +1907,104 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]]
name = "hyper"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
"hyper",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"libc",
"pin-project-lite",
"socket2 0.6.0",
"tokio",
"tower-service",
"tracing",
]
[[package]]
name = "iana-time-zone"
version = "0.1.62"
@@ -2459,6 +2720,12 @@ dependencies = [
"regex-automata",
]
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "md-5"
version = "0.10.6"
@@ -2548,6 +2815,12 @@ dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimad"
version = "0.13.1"
@@ -2557,6 +2830,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
@@ -2672,6 +2951,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "notify"
version = "8.0.0"
@@ -2902,6 +3191,26 @@ dependencies = [
"sha2",
]
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -2926,7 +3235,7 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d"
dependencies = [
"base64",
"base64 0.22.1",
"indexmap 2.11.1",
"quick-xml 0.32.0",
"serde",
@@ -3090,6 +3399,16 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "prost"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
dependencies = [
"bytes",
"prost-derive 0.13.5",
]
[[package]]
name = "prost"
version = "0.14.1"
@@ -3097,7 +3416,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d"
dependencies = [
"bytes",
"prost-derive",
"prost-derive 0.14.1",
]
[[package]]
name = "prost-derive"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools 0.14.0",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
@@ -3113,6 +3445,15 @@ dependencies = [
"syn 2.0.100",
]
[[package]]
name = "prost-types"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
dependencies = [
"prost 0.13.5",
]
[[package]]
name = "py-turso"
version = "0.3.0-pre.1"
@@ -3818,6 +4159,16 @@ dependencies = [
"serde",
]
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.0"
@@ -3984,6 +4335,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "synstructure"
version = "0.13.1"
@@ -4224,8 +4581,9 @@ dependencies = [
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"socket2 0.6.0",
"tokio-macros",
"tracing",
"windows-sys 0.59.0",
]
@@ -4240,6 +4598,30 @@ dependencies = [
"syn 2.0.100",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml"
version = "0.8.22"
@@ -4282,6 +4664,82 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
[[package]]
name = "tonic"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [
"async-stream",
"async-trait",
"axum",
"base64 0.22.1",
"bytes",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-timeout",
"hyper-util",
"percent-encoding",
"pin-project",
"prost 0.13.5",
"socket2 0.5.10",
"tokio",
"tokio-stream",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand 0.8.5",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
@@ -4355,6 +4813,12 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "turso"
version = "0.3.0-pre.1"
@@ -4571,13 +5035,13 @@ dependencies = [
name = "turso_sync_engine"
version = "0.3.0-pre.1"
dependencies = [
"base64",
"base64 0.22.1",
"bytes",
"ctor 0.4.2",
"futures",
"genawaiter",
"http",
"prost",
"prost 0.14.1",
"rand 0.9.2",
"rand_chacha 0.9.0",
"roaring",
@@ -4822,6 +5286,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -5201,6 +5674,7 @@ name = "write-throughput"
version = "0.1.0"
dependencies = [
"clap",
"console-subscriber",
"futures",
"tokio",
"tracing-subscriber",

View File

@@ -99,6 +99,7 @@ regex-syntax = { version = "0.8.5", default-features = false }
similar = { version = "2.7.0" }
similar-asserts = { version = "1.7.0" }
bitmaps = { version = "3.2.1", default-features = false }
console-subscriber = { version = "0.4.1" }
[profile.dev.package.similar]
opt-level = 3

View File

@@ -46,10 +46,13 @@ pub use params::params_from_iter;
pub use params::IntoParams;
use std::fmt::Debug;
use std::future::Future;
use std::num::NonZero;
use std::sync::{Arc, Mutex};
use std::task::Poll;
pub use turso_core::EncryptionOpts;
use turso_core::OpenFlags;
// Re-exports rows
pub use crate::rows::{Row, Rows};
@@ -464,6 +467,45 @@ impl Clone for Statement {
unsafe impl Send for Statement {}
unsafe impl Sync for Statement {}
struct Execute {
stmt: Arc<Mutex<turso_core::Statement>>,
}
unsafe impl Send for Execute {}
unsafe impl Sync for Execute {}
impl Future for Execute {
type Output = Result<u64>;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let mut stmt = self.stmt.lock().unwrap();
match stmt.step_with_waker(cx.waker()) {
Ok(turso_core::StepResult::Row) => Poll::Ready(Err(Error::SqlExecutionFailure(
"unexpected row during execution".to_string(),
))),
Ok(turso_core::StepResult::Done) => {
let changes = stmt.n_change();
assert!(changes >= 0);
Poll::Ready(Ok(changes as u64))
}
Ok(turso_core::StepResult::IO) => {
stmt.run_once()?;
Poll::Pending
}
Ok(turso_core::StepResult::Busy) => Poll::Ready(Err(Error::SqlExecutionFailure(
"database is locked".to_string(),
))),
Ok(turso_core::StepResult::Interrupt) => {
Poll::Ready(Err(Error::SqlExecutionFailure("interrupted".to_string())))
}
Err(err) => Poll::Ready(Err(err.into())),
}
}
}
impl Statement {
/// Query the database with this prepared statement.
pub async fn query(&mut self, params: impl IntoParams) -> Result<Rows> {
@@ -514,33 +556,11 @@ impl Statement {
}
}
}
loop {
let mut stmt = self.inner.lock().unwrap();
match stmt.step() {
Ok(turso_core::StepResult::Row) => {
return Err(Error::SqlExecutionFailure(
"unexpected row during execution".to_string(),
));
}
Ok(turso_core::StepResult::Done) => {
let changes = stmt.n_change();
assert!(changes >= 0);
return Ok(changes as u64);
}
Ok(turso_core::StepResult::IO) => {
stmt.run_once()?;
}
Ok(turso_core::StepResult::Busy) => {
return Err(Error::SqlExecutionFailure("database is locked".to_string()));
}
Ok(turso_core::StepResult::Interrupt) => {
return Err(Error::SqlExecutionFailure("interrupted".to_string()));
}
Err(err) => {
return Err(err.into());
}
}
}
let execute = Execute {
stmt: self.inner.clone(),
};
execute.await
}
/// Returns columns of the result of this prepared statement.

View File

@@ -2,7 +2,9 @@ use turso_core::types::FromValue;
use crate::{Error, Result, Value};
use std::fmt::Debug;
use std::future::Future;
use std::sync::{Arc, Mutex};
use std::task::Poll;
/// Results of a prepared statement query.
pub struct Rows {
@@ -28,33 +30,50 @@ impl Rows {
}
/// Fetch the next row of this result set.
pub async fn next(&mut self) -> Result<Option<Row>> {
loop {
let mut stmt = self
.inner
.lock()
.map_err(|e| Error::MutexError(e.to_string()))?;
match stmt.step()? {
turso_core::StepResult::Row => {
let row = stmt.row().unwrap();
return Ok(Some(Row {
values: row.get_values().map(|v| v.to_owned()).collect(),
}));
}
turso_core::StepResult::Done => return Ok(None),
turso_core::StepResult::IO => {
if let Err(e) = stmt.run_once() {
return Err(e.into());
struct Next {
stmt: Arc<Mutex<turso_core::Statement>>,
}
impl Future for Next {
type Output = Result<Option<Row>>;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let mut stmt = self
.stmt
.lock()
.map_err(|e| Error::MutexError(e.to_string()))?;
match stmt.step_with_waker(cx.waker())? {
turso_core::StepResult::Row => {
let row = stmt.row().unwrap();
Poll::Ready(Ok(Some(Row {
values: row.get_values().map(|v| v.to_owned()).collect(),
})))
}
turso_core::StepResult::Done => Poll::Ready(Ok(None)),
turso_core::StepResult::IO => {
stmt.run_once()?;
Poll::Pending
}
turso_core::StepResult::Busy => Poll::Ready(Err(Error::SqlExecutionFailure(
"database is locked".to_string(),
))),
turso_core::StepResult::Interrupt => {
Poll::Ready(Err(Error::SqlExecutionFailure("interrupted".to_string())))
}
continue;
}
turso_core::StepResult::Busy => {
return Err(Error::SqlExecutionFailure("database is locked".to_string()))
}
turso_core::StepResult::Interrupt => {
return Err(Error::SqlExecutionFailure("interrupted".to_string()))
}
}
}
unsafe impl Send for Next {}
let next = Next {
stmt: self.inner.clone(),
};
next.await
}
}

View File

@@ -402,7 +402,8 @@ async fn test_concurrent_unique_constraint_regression() {
match result {
Ok(_) => (),
Err(Error::SqlExecutionFailure(e))
if e.contains("UNIQUE constraint failed") => {}
if e.contains("UNIQUE constraint failed")
| e.contains("database is locked") => {}
Err(e) => {
panic!("Error executing statement: {e:?}");
}

View File

@@ -87,3 +87,15 @@ impl std::ops::Sub<Duration> for Instant {
pub trait Clock {
fn now(&self) -> Instant;
}
pub struct DefaultClock;
impl Clock for DefaultClock {
fn now(&self) -> Instant {
let now = chrono::Local::now();
Instant {
secs: now.timestamp(),
micros: now.timestamp_subsec_micros(),
}
}
}

View File

@@ -1,4 +1,6 @@
use crate::{Clock, Completion, File, Instant, LimboError, OpenFlags, Result, IO};
use crate::{
io::clock::DefaultClock, Clock, Completion, File, Instant, LimboError, OpenFlags, Result, IO,
};
use parking_lot::RwLock;
use std::io::{Read, Seek, Write};
use std::sync::Arc;
@@ -44,11 +46,7 @@ impl IO for GenericIO {
impl Clock for GenericIO {
fn now(&self) -> Instant {
let now = chrono::Local::now();
Instant {
secs: now.timestamp(),
micros: now.timestamp_subsec_micros(),
}
DefaultClock.now()
}
}

View File

@@ -1,7 +1,7 @@
#![allow(clippy::arc_with_non_send_sync)]
use super::{common, Completion, CompletionInner, File, OpenFlags, IO};
use crate::io::clock::{Clock, Instant};
use crate::io::clock::{Clock, DefaultClock, Instant};
use crate::storage::wal::CKPT_BATCH_PAGES;
use crate::{turso_assert, CompletionError, LimboError, Result};
use parking_lot::Mutex;
@@ -697,11 +697,7 @@ impl IO for UringIO {
impl Clock for UringIO {
fn now(&self) -> Instant {
let now = chrono::Local::now();
Instant {
secs: now.timestamp(),
micros: now.timestamp_subsec_micros(),
}
DefaultClock.now()
}
}

View File

@@ -1,5 +1,5 @@
use super::{Buffer, Clock, Completion, File, OpenFlags, IO};
use crate::Result;
use crate::{io::clock::DefaultClock, Result};
use crate::io::clock::Instant;
use std::{
@@ -35,11 +35,7 @@ impl Default for MemoryIO {
impl Clock for MemoryIO {
fn now(&self) -> Instant {
let now = chrono::Local::now();
Instant {
secs: now.timestamp(),
micros: now.timestamp_subsec_micros(),
}
DefaultClock.now()
}
}

View File

@@ -3,11 +3,13 @@ use crate::storage::sqlite3_ondisk::WAL_FRAME_HEADER_SIZE;
use crate::{BufferPool, CompletionError, Result};
use bitflags::bitflags;
use cfg_block::cfg_block;
use parking_lot::Mutex;
use std::cell::RefCell;
use std::fmt;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
use std::task::Waker;
use std::{fmt::Debug, pin::Pin};
pub trait File: Send + Sync {
@@ -139,12 +141,64 @@ pub struct Completion {
inner: Option<Arc<CompletionInner>>,
}
#[derive(Debug, Default)]
struct ContextInner {
waker: Option<Waker>,
// TODO: add abort signal
}
#[derive(Debug, Clone)]
pub struct Context {
inner: Arc<Mutex<ContextInner>>,
}
impl ContextInner {
pub fn new() -> Self {
Self { waker: None }
}
pub fn wake(&mut self) {
if let Some(waker) = self.waker.take() {
waker.wake();
}
}
pub fn set_waker(&mut self, waker: &Waker) {
if let Some(curr_waker) = self.waker.as_mut() {
// only call and change waker if it would awake a different task
if !curr_waker.will_wake(waker) {
let prev_waker = std::mem::replace(curr_waker, waker.clone());
prev_waker.wake();
}
} else {
self.waker = Some(waker.clone());
}
}
}
impl Context {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(ContextInner::new())),
}
}
pub fn wake(&self) {
self.inner.lock().wake();
}
pub fn set_waker(&self, waker: &Waker) {
self.inner.lock().set_waker(waker);
}
}
struct CompletionInner {
completion_type: CompletionType,
/// None means we completed successfully
// Thread safe with OnceLock
result: std::sync::OnceLock<Option<CompletionError>>,
needs_link: bool,
context: Context,
/// Optional parent group this completion belongs to
parent: OnceLock<Arc<GroupCompletionInner>>,
}
@@ -293,26 +347,28 @@ pub enum CompletionType {
Yield,
}
impl CompletionInner {
fn new(completion_type: CompletionType, needs_link: bool) -> Self {
Self {
completion_type,
result: OnceLock::new(),
needs_link,
context: Context::new(),
parent: OnceLock::new(),
}
}
}
impl Completion {
pub fn new(completion_type: CompletionType) -> Self {
Self {
inner: Some(Arc::new(CompletionInner {
completion_type,
result: OnceLock::new(),
needs_link: false,
parent: OnceLock::new(),
})),
inner: Some(Arc::new(CompletionInner::new(completion_type, false))),
}
}
pub fn new_linked(completion_type: CompletionType) -> Self {
Self {
inner: Some(Arc::new(CompletionInner {
completion_type,
result: OnceLock::new(),
needs_link: true,
parent: OnceLock::new(),
})),
inner: Some(Arc::new(CompletionInner::new(completion_type, true))),
}
}
@@ -375,6 +431,18 @@ impl Completion {
Self { inner: None }
}
pub fn wake(&self) {
self.get_inner().context.wake();
}
pub fn set_waker(&self, waker: &Waker) {
if self.finished() || self.inner.is_none() {
waker.wake_by_ref();
} else {
self.get_inner().context.set_waker(waker);
}
}
pub fn succeeded(&self) -> bool {
match &self.inner {
Some(inner) => match &inner.completion_type {
@@ -465,6 +533,8 @@ impl Completion {
result.err()
});
// call the waker regardless
inner.context.wake();
}
/// only call this method if you are sure that the completion is

View File

@@ -1,6 +1,6 @@
use super::{Completion, File, OpenFlags, IO};
use crate::error::LimboError;
use crate::io::clock::{Clock, Instant};
use crate::io::clock::{Clock, DefaultClock, Instant};
use crate::io::common;
use crate::Result;
use parking_lot::Mutex;
@@ -27,11 +27,7 @@ impl UnixIO {
impl Clock for UnixIO {
fn now(&self) -> Instant {
let now = chrono::Local::now();
Instant {
secs: now.timestamp(),
micros: now.timestamp_subsec_micros(),
}
DefaultClock.now()
}
}

View File

@@ -1,6 +1,6 @@
use super::{Buffer, Completion, File, OpenFlags, IO};
use crate::ext::VfsMod;
use crate::io::clock::{Clock, Instant};
use crate::io::clock::{Clock, DefaultClock, Instant};
use crate::io::CompletionInner;
use crate::{LimboError, Result};
use std::ffi::{c_void, CString};
@@ -10,11 +10,7 @@ use turso_ext::{BufferRef, IOCallback, SendPtr, VfsFileImpl, VfsImpl};
impl Clock for VfsMod {
fn now(&self) -> Instant {
let now = chrono::Local::now();
Instant {
secs: now.timestamp(),
micros: now.timestamp_subsec_micros(),
}
DefaultClock.now()
}
}

View File

@@ -1,4 +1,6 @@
use crate::{Clock, Completion, File, Instant, LimboError, OpenFlags, Result, IO};
use crate::{
io::clock::DefaultClock, Clock, Completion, File, Instant, LimboError, OpenFlags, Result, IO,
};
use parking_lot::RwLock;
use std::io::{Read, Seek, Write};
use std::sync::Arc;
@@ -44,11 +46,7 @@ impl IO for WindowsIO {
impl Clock for WindowsIO {
fn now(&self) -> Instant {
let now = chrono::Local::now();
Instant {
secs: now.timestamp(),
micros: now.timestamp_subsec_micros(),
}
DefaultClock.now()
}
}

View File

@@ -63,6 +63,7 @@ pub use io::{
};
use parking_lot::RwLock;
use schema::Schema;
use std::task::Waker;
use std::{
borrow::Cow,
cell::{Cell, RefCell},
@@ -2437,7 +2438,7 @@ impl BusyTimeout {
}
}
self.iteration += 1;
self.iteration = self.iteration.saturating_add(1);
self.timeout = now + delay;
}
}
@@ -2509,10 +2510,13 @@ impl Statement {
self.state.interrupt();
}
pub fn step(&mut self) -> Result<StepResult> {
fn _step(&mut self, waker: Option<&Waker>) -> Result<StepResult> {
if let Some(busy_timeout) = self.busy_timeout.as_ref() {
if self.pager.io.now() < busy_timeout.timeout {
// Yield the query as the timeout has not been reached yet
if let Some(waker) = waker {
waker.wake_by_ref();
}
return Ok(StepResult::IO);
}
}
@@ -2523,6 +2527,7 @@ impl Statement {
self.mv_store.as_ref(),
self.pager.clone(),
self.query_mode,
waker,
)
} else {
const MAX_SCHEMA_RETRY: usize = 50;
@@ -2531,6 +2536,7 @@ impl Statement {
self.mv_store.as_ref(),
self.pager.clone(),
self.query_mode,
waker,
);
for attempt in 0..MAX_SCHEMA_RETRY {
// Only reprepare if we still need to update schema
@@ -2544,6 +2550,7 @@ impl Statement {
self.mv_store.as_ref(),
self.pager.clone(),
self.query_mode,
waker,
);
}
res
@@ -2574,6 +2581,9 @@ impl Statement {
};
if now < self.busy_timeout.as_ref().unwrap().timeout {
if let Some(waker) = waker {
waker.wake_by_ref();
}
res = Ok(StepResult::IO);
}
}
@@ -2581,6 +2591,14 @@ impl Statement {
res
}
pub fn step(&mut self) -> Result<StepResult> {
self._step(None)
}
pub fn step_with_waker(&mut self, waker: &Waker) -> Result<StepResult> {
self._step(Some(waker))
}
pub(crate) fn run_ignore_rows(&mut self) -> Result<()> {
loop {
match self.step()? {

View File

@@ -17,6 +17,7 @@ use crate::vdbe::Register;
use crate::vtab::VirtualTableCursor;
use crate::{turso_assert, Completion, CompletionError, Result, IO};
use std::fmt::{Debug, Display};
use std::task::Waker;
/// SQLite by default uses 2000 as maximum numbers in a row.
/// It controlld by the constant called SQLITE_MAX_COLUMN
@@ -2394,6 +2395,17 @@ impl IOCompletions {
IOCompletions::Many(completions) => completions.iter().find_map(|c| c.get_error()),
}
}
pub fn set_waker(&self, waker: Option<&Waker>) {
if let Some(waker) = waker {
match self {
IOCompletions::Single(c) => c.set_waker(waker),
IOCompletions::Many(completions) => {
completions.iter().for_each(|c| c.set_waker(waker))
}
}
}
}
}
#[derive(Debug)]

View File

@@ -69,6 +69,7 @@ use std::{
atomic::{AtomicI64, Ordering},
Arc,
},
task::Waker,
};
use tracing::{instrument, Level};
@@ -530,9 +531,10 @@ impl Program {
mv_store: Option<&Arc<MvStore>>,
pager: Arc<Pager>,
query_mode: QueryMode,
waker: Option<&Waker>,
) -> Result<StepResult> {
match query_mode {
QueryMode::Normal => self.normal_step(state, mv_store, pager),
QueryMode::Normal => self.normal_step(state, mv_store, pager, waker),
QueryMode::Explain => self.explain_step(state, mv_store, pager),
QueryMode::ExplainQueryPlan => self.explain_query_plan_step(state, mv_store, pager),
}
@@ -645,6 +647,7 @@ impl Program {
state: &mut ProgramState,
mv_store: Option<&Arc<MvStore>>,
pager: Arc<Pager>,
waker: Option<&Waker>,
) -> Result<StepResult> {
let enable_tracing = tracing::enabled!(tracing::Level::TRACE);
loop {
@@ -662,6 +665,7 @@ impl Program {
}
if let Some(io) = &state.io_completions {
if !io.finished() {
io.set_waker(waker);
return Ok(StepResult::IO);
}
if let Some(err) = io.get_error() {
@@ -694,6 +698,7 @@ impl Program {
}
Ok(InsnFunctionStepResult::IO(io)) => {
// Instruction not complete - waiting for I/O, will resume at same PC
io.set_waker(waker);
state.io_completions = Some(io);
return Ok(StepResult::IO);
}

View File

@@ -7,9 +7,13 @@ edition = "2021"
name = "write-throughput"
path = "src/main.rs"
[features]
console = ["dep:console-subscriber" ,"tokio/tracing"]
[dependencies]
turso = { workspace = true }
clap = { workspace = true, features = ["derive"] }
tokio = { workspace = true, default-features = true, features = ["full"] }
futures = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
console-subscriber = { workspace = true, optional = true }

View File

@@ -2,7 +2,9 @@ use clap::{Parser, ValueEnum};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Barrier};
use std::time::{Duration, Instant};
use tracing_subscriber::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Layer};
use turso::{Builder, Database, Result};
#[derive(Debug, Clone, Copy, ValueEnum)]
@@ -53,11 +55,18 @@ struct Args {
}
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
#[cfg(feature = "console")]
let console_layer = console_subscriber::spawn();
let fmt_layer = tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_thread_ids(true)
.init();
.with_filter(EnvFilter::from_default_env());
let registry = tracing_subscriber::registry();
#[cfg(feature = "console")]
let registry = registry.with(console_layer);
let registry = registry.with(fmt_layer);
registry.init();
let args = Args::parse();
let rt = tokio::runtime::Builder::new_multi_thread()