Skip to content

Commit bb62cf7

Browse files
committed
Fix segfault due to race condition in sqlite (#1300)
1 parent 9643d38 commit bb62cf7

File tree

3 files changed

+94
-27
lines changed

3 files changed

+94
-27
lines changed

sqlx-core/src/sqlite/statement/handle.rs

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
1+
use either::Either;
12
use std::ffi::c_void;
23
use std::ffi::CStr;
4+
use std::hint;
35
use std::os::raw::{c_char, c_int};
46
use std::ptr;
57
use std::ptr::NonNull;
68
use std::slice::from_raw_parts;
79
use std::str::{from_utf8, from_utf8_unchecked};
10+
use std::sync::atomic::{AtomicU8, Ordering};
811

912
use libsqlite3_sys::{
1013
sqlite3, sqlite3_bind_blob64, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64,
1114
sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name,
12-
sqlite3_bind_text64, sqlite3_changes, sqlite3_column_blob, sqlite3_column_bytes,
13-
sqlite3_column_count, sqlite3_column_database_name, sqlite3_column_decltype,
14-
sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64, sqlite3_column_name,
15-
sqlite3_column_origin_name, sqlite3_column_table_name, sqlite3_column_type,
16-
sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset, sqlite3_sql,
17-
sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata, sqlite3_value,
18-
SQLITE_MISUSE, SQLITE_OK, SQLITE_TRANSIENT, SQLITE_UTF8,
15+
sqlite3_bind_text64, sqlite3_changes, sqlite3_clear_bindings, sqlite3_column_blob,
16+
sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_database_name,
17+
sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64,
18+
sqlite3_column_name, sqlite3_column_origin_name, sqlite3_column_table_name,
19+
sqlite3_column_type, sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset,
20+
sqlite3_sql, sqlite3_step, sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata,
21+
sqlite3_value, SQLITE_DONE, SQLITE_MISUSE, SQLITE_OK, SQLITE_ROW, SQLITE_TRANSIENT,
22+
SQLITE_UTF8,
1923
};
2024

2125
use crate::error::{BoxDynError, Error};
2226
use crate::sqlite::type_info::DataType;
2327
use crate::sqlite::{SqliteError, SqliteTypeInfo};
2428

2529
#[derive(Debug)]
26-
pub(crate) struct StatementHandle(pub(super) NonNull<sqlite3_stmt>);
30+
pub(crate) struct StatementHandle(NonNull<sqlite3_stmt>, Lock);
2731

2832
// access to SQLite3 statement handles are safe to send and share between threads
2933
// as long as the `sqlite3_step` call is serialized.
@@ -32,6 +36,10 @@ unsafe impl Send for StatementHandle {}
3236
unsafe impl Sync for StatementHandle {}
3337

3438
impl StatementHandle {
39+
pub(super) fn new(ptr: NonNull<sqlite3_stmt>) -> Self {
40+
Self(ptr, Lock::new())
41+
}
42+
3543
#[inline]
3644
pub(super) unsafe fn db_handle(&self) -> *mut sqlite3 {
3745
// O(c) access to the connection handle for this statement handle
@@ -280,8 +288,37 @@ impl StatementHandle {
280288
Ok(from_utf8(self.column_blob(index))?)
281289
}
282290

291+
pub(crate) fn step(&self) -> Result<Either<u64, ()>, Error> {
292+
self.1.enter_step();
293+
294+
let status = unsafe { sqlite3_step(self.0.as_ptr()) };
295+
let result = match status {
296+
SQLITE_ROW => Ok(Either::Right(())),
297+
SQLITE_DONE => Ok(Either::Left(self.changes())),
298+
_ => Err(self.last_error().into()),
299+
};
300+
301+
if self.1.exit_step() {
302+
unsafe { sqlite3_reset(self.0.as_ptr()) };
303+
self.1.exit_reset();
304+
}
305+
306+
result
307+
}
308+
283309
pub(crate) fn reset(&self) {
310+
if !self.1.enter_reset() {
311+
// reset or step already in progress
312+
return;
313+
}
314+
284315
unsafe { sqlite3_reset(self.0.as_ptr()) };
316+
317+
self.1.exit_reset();
318+
}
319+
320+
pub(crate) fn clear_bindings(&self) {
321+
unsafe { sqlite3_clear_bindings(self.0.as_ptr()) };
285322
}
286323
}
287324
impl Drop for StatementHandle {
@@ -301,3 +338,44 @@ impl Drop for StatementHandle {
301338
}
302339
}
303340
}
341+
342+
const RESET: u8 = 0b0000_0001;
343+
const STEP: u8 = 0b0000_0010;
344+
345+
// Lock to synchronize calls to `step` and `reset`.
346+
#[derive(Debug)]
347+
struct Lock(AtomicU8);
348+
349+
impl Lock {
350+
fn new() -> Self {
351+
Self(AtomicU8::new(0))
352+
}
353+
354+
// If this returns `true` reset can be performed, otherwise reset must be delayed until the
355+
// current step finishes and `exit_step` is called.
356+
fn enter_reset(&self) -> bool {
357+
self.0.fetch_or(RESET, Ordering::Acquire) == 0
358+
}
359+
360+
fn exit_reset(&self) {
361+
self.0.fetch_and(!RESET, Ordering::Release);
362+
}
363+
364+
fn enter_step(&self) {
365+
// NOTE: spin loop should be fine here as we are only waiting for a `reset` to finish which
366+
// should be quick.
367+
while self
368+
.0
369+
.compare_exchange(0, STEP, Ordering::Acquire, Ordering::Relaxed)
370+
.is_err()
371+
{
372+
hint::spin_loop();
373+
}
374+
}
375+
376+
// If this returns `true` it means a previous attempt to reset was delayed and must be
377+
// performed now (followed by `exit_reset`).
378+
fn exit_step(&self) -> bool {
379+
self.0.fetch_and(!STEP, Ordering::Release) & RESET != 0
380+
}
381+
}

sqlx-core/src/sqlite/statement/virtual.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ use crate::sqlite::{SqliteColumn, SqliteError, SqliteRow, SqliteValue};
88
use crate::HashMap;
99
use bytes::{Buf, Bytes};
1010
use libsqlite3_sys::{
11-
sqlite3, sqlite3_clear_bindings, sqlite3_prepare_v3, sqlite3_reset, sqlite3_stmt, SQLITE_OK,
12-
SQLITE_PREPARE_PERSISTENT,
11+
sqlite3, sqlite3_prepare_v3, sqlite3_stmt, SQLITE_OK, SQLITE_PREPARE_PERSISTENT,
1312
};
1413
use smallvec::SmallVec;
1514
use std::i32;
@@ -92,7 +91,7 @@ fn prepare(
9291
query.advance(n);
9392

9493
if let Some(handle) = NonNull::new(statement_handle) {
95-
return Ok(Some(StatementHandle(handle)));
94+
return Ok(Some(StatementHandle::new(handle)));
9695
}
9796
}
9897

@@ -183,13 +182,11 @@ impl VirtualStatement {
183182
for (i, handle) in self.handles.iter().enumerate() {
184183
SqliteRow::inflate_if_needed(&handle, &self.columns[i], self.last_row_values[i].take());
185184

186-
unsafe {
187-
// Reset A Prepared Statement Object
188-
// https://www.sqlite.org/c3ref/reset.html
189-
// https://www.sqlite.org/c3ref/clear_bindings.html
190-
sqlite3_reset(handle.0.as_ptr());
191-
sqlite3_clear_bindings(handle.0.as_ptr());
192-
}
185+
// Reset A Prepared Statement Object
186+
// https://www.sqlite.org/c3ref/reset.html
187+
// https://www.sqlite.org/c3ref/clear_bindings.html
188+
handle.reset();
189+
handle.clear_bindings();
193190
}
194191
}
195192
}

sqlx-core/src/sqlite/statement/worker.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use crate::sqlite::statement::StatementHandle;
33
use crossbeam_channel::{unbounded, Sender};
44
use either::Either;
55
use futures_channel::oneshot;
6-
use libsqlite3_sys::{sqlite3_step, SQLITE_DONE, SQLITE_ROW};
76
use std::sync::{Arc, Weak};
87
use std::thread;
98

@@ -33,14 +32,7 @@ impl StatementWorker {
3332
match cmd {
3433
StatementWorkerCommand::Step { statement, tx } => {
3534
let resp = if let Some(statement) = statement.upgrade() {
36-
let status = unsafe { sqlite3_step(statement.0.as_ptr()) };
37-
38-
let resp = match status {
39-
SQLITE_ROW => Ok(Either::Right(())),
40-
SQLITE_DONE => Ok(Either::Left(statement.changes())),
41-
_ => Err(statement.last_error().into()),
42-
};
43-
resp
35+
statement.step()
4436
} else {
4537
// Statement is already finalized.
4638
Err(Error::WorkerCrashed)

0 commit comments

Comments
 (0)