This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new d162dd8a5 feat(rust): define cancellation in a sensible way (#3905)
d162dd8a5 is described below

commit d162dd8a5d90623b593097c6c037de6a176b405c
Author: David Li <[email protected]>
AuthorDate: Fri Aug 7 08:39:50 2026 +0900

    feat(rust): define cancellation in a sensible way (#3905)
    
    Closes #3454.
---
 .github/workflows/rust.yml                         |   6 +
 rust/core/src/sync.rs                              |  53 ++-
 rust/driver/dummy/src/lib.rs                       |  20 ++
 rust/driver/dummy/tests/driver_exporter_dummy.rs   |  35 +-
 rust/driver_manager/src/lib.rs                     | 392 +++++++++++++++++----
 rust/driver_manager/tests/driver_manager_sqlite.rs |  10 +-
 rust/ffi/src/driver_exporter.rs                    |  52 ++-
 7 files changed, 461 insertions(+), 107 deletions(-)

diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 74d685853..024bfd833 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -181,14 +181,20 @@ jobs:
             -Zcrate-attr='feature(non_exhaustive_omitted_patterns_lint)' \
             -Zcrate-attr='allow(unused_features)'
       - name: Test (Default Features)
+        env:
+          RUST_BACKTRACE: "1"
         working-directory: rust
         run: >
           cargo test --all-targets --workspace
       - name: Test (All Features)
+        env:
+          RUST_BACKTRACE: "1"
         working-directory: rust
         run: >
           cargo test --all-targets --all-features --workspace
       - name: Doctests
+        env:
+          RUST_BACKTRACE: "1"
         working-directory: rust
         run: >
           cargo test --doc --all-features --workspace
diff --git a/rust/core/src/sync.rs b/rust/core/src/sync.rs
index e14c6b224..55c5f19bd 100644
--- a/rust/core/src/sync.rs
+++ b/rust/core/src/sync.rs
@@ -21,7 +21,7 @@ use arrow_array::{RecordBatch, RecordBatchReader};
 use arrow_schema::Schema;
 
 use crate::PartitionedResult;
-use crate::error::Result;
+use crate::error::{Error, Result, Status};
 use crate::options::{self, OptionConnection, OptionDatabase, OptionStatement, 
OptionValue};
 
 /// Ability to configure an object by setting/getting options.
@@ -44,6 +44,28 @@ pub trait Optionable {
     fn get_option_double(&self, key: Self::Option) -> Result<f64>;
 }
 
+/// A handle to cancel an in-progress operation.
+///
+/// This is a separated handle because otherwise it would be impossible to
+/// safely call a `cancel` method on a database/connection/statement itself
+/// due to the borrow checker.
+pub trait CancelHandle: Send + Sync {
+    /// Attempt to cancel the in-progress operation (best-effort).
+    fn try_cancel(&self) -> Result<()>;
+}
+
+/// A cancellation handle that does nothing (because cancellation is 
unsupported).
+pub struct NoOpCancellationHandle;
+
+impl CancelHandle for NoOpCancellationHandle {
+    fn try_cancel(&self) -> Result<()> {
+        Err(Error::with_message_and_status(
+            "cancellation not implemented",
+            Status::Unknown,
+        ))
+    }
+}
+
 /// A handle to an ADBC driver.
 pub trait Driver {
     type DatabaseType: Database;
@@ -76,6 +98,11 @@ pub trait Database: Optionable<Option = OptionDatabase> {
         &self,
         opts: impl IntoIterator<Item = (options::OptionConnection, 
OptionValue)>,
     ) -> Result<Self::ConnectionType>;
+
+    /// Get a handle to cancel operations on this database.
+    fn get_cancel_handle(&self) -> Box<dyn CancelHandle> {
+        Box::new(NoOpCancellationHandle {})
+    }
 }
 
 /// A handle to an ADBC connection.
@@ -95,7 +122,15 @@ pub trait Connection: Optionable<Option = OptionConnection> 
{
     fn new_statement(&mut self) -> Result<Self::StatementType>;
 
     /// Cancel the in-progress operation on a connection.
-    fn cancel(&mut self) -> Result<()>;
+    #[deprecated(since = "0.25.0", note = "Use get_cancel_handle() instead")]
+    fn cancel(&mut self) -> Result<()> {
+        self.get_cancel_handle().try_cancel()
+    }
+
+    /// Get a handle to cancel operations on this connection.
+    fn get_cancel_handle(&self) -> Box<dyn CancelHandle> {
+        Box::new(NoOpCancellationHandle {})
+    }
 
     /// Get metadata about the database/driver.
     ///
@@ -456,12 +491,20 @@ pub trait Statement: Optionable<Option = OptionStatement> 
{
     fn set_substrait_plan(&mut self, plan: impl AsRef<[u8]>) -> Result<()>;
 
     /// Cancel execution of an in-progress query.
+    #[deprecated(since = "0.25.0", note = "Use get_cancel_handle() instead")]
+    fn cancel(&mut self) -> Result<()> {
+        self.get_cancel_handle().try_cancel()
+    }
+
+    /// Get a handle to cancel operations on this statement.
     ///
-    /// This can be called during [Statement::execute] (or similar), or while
-    /// consuming a result set returned from such.
+    /// The resulting handle can be called during [Statement::execute] (or
+    /// similar), or while consuming a result set returned from such.
     ///
     /// # Since
     ///
     /// ADBC API revision 1.1.0
-    fn cancel(&mut self) -> Result<()>;
+    fn get_cancel_handle(&self) -> Box<dyn CancelHandle> {
+        Box::new(NoOpCancellationHandle {})
+    }
 }
diff --git a/rust/driver/dummy/src/lib.rs b/rust/driver/dummy/src/lib.rs
index 8c3b92e76..ad8fedd34 100644
--- a/rust/driver/dummy/src/lib.rs
+++ b/rust/driver/dummy/src/lib.rs
@@ -308,6 +308,26 @@ impl Connection for DummyConnection {
         Err(error)
     }
 
+    /// This method is used to test that errors round-trip correctly.
+    fn get_cancel_handle(&self) -> Box<dyn adbc_core::CancelHandle> {
+        struct CancelHandle;
+
+        impl adbc_core::CancelHandle for CancelHandle {
+            fn try_cancel(&self) -> Result<()> {
+                let mut error = Error::with_message_and_status("message", 
Status::Cancelled);
+                error.vendor_code = 
constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA;
+                error.sqlstate = [1, 2, 3, 4, 5];
+                error.details = Some(vec![
+                    ("key1".into(), b"AAA".into()),
+                    ("key2".into(), b"ZZZZZ".into()),
+                ]);
+                Err(error)
+            }
+        }
+
+        Box::new(CancelHandle)
+    }
+
     fn commit(&mut self) -> Result<()> {
         Ok(())
     }
diff --git a/rust/driver/dummy/tests/driver_exporter_dummy.rs 
b/rust/driver/dummy/tests/driver_exporter_dummy.rs
index 36e7657f9..ecb7ff893 100644
--- a/rust/driver/dummy/tests/driver_exporter_dummy.rs
+++ b/rust/driver/dummy/tests/driver_exporter_dummy.rs
@@ -465,11 +465,14 @@ fn test_connection_get_info_ignores_unrecognized_codes() {
 
 #[test]
 fn test_connection_cancel() {
-    let (_, _, mut exported_connection, _) = get_exported();
-    let (_, _, mut native_connection, _) = get_native();
+    let (_, _, exported_connection, _) = get_exported();
+    let (_, _, native_connection, _) = get_native();
 
-    let exported_error = exported_connection.cancel().unwrap_err();
-    let native_error = native_connection.cancel().unwrap_err();
+    let exported_handle = exported_connection.get_cancel_handle();
+    let native_handle = native_connection.get_cancel_handle();
+
+    let exported_error = exported_handle.try_cancel().unwrap_err();
+    let native_error = native_handle.try_cancel().unwrap_err();
 
     assert_eq!(exported_error, native_error);
 }
@@ -668,11 +671,27 @@ fn test_statement_bind_stream() {
 
 #[test]
 fn test_statement_cancel() {
-    let (_, _, _, mut exported_statement) = get_exported();
-    let (_, _, _, mut native_statement) = get_native();
+    let (_, _, _, exported_statement) = get_exported();
+    let (_, _, _, native_statement) = get_native();
+
+    let exported_handle = exported_statement.get_cancel_handle();
+    let native_handle = native_statement.get_cancel_handle();
 
-    exported_statement.cancel().unwrap();
-    native_statement.cancel().unwrap();
+    let res = exported_handle.try_cancel();
+    assert!(res.is_err());
+    assert!(
+        res.unwrap_err()
+            .to_string()
+            .contains("cancellation not implemented")
+    );
+
+    let res = native_handle.try_cancel();
+    assert!(res.is_err());
+    assert!(
+        res.unwrap_err()
+            .to_string()
+            .contains("cancellation not implemented")
+    );
 }
 
 #[test]
diff --git a/rust/driver_manager/src/lib.rs b/rust/driver_manager/src/lib.rs
index bb2cc700d..811886828 100644
--- a/rust/driver_manager/src/lib.rs
+++ b/rust/driver_manager/src/lib.rs
@@ -346,11 +346,16 @@ struct ManagedDatabaseInner {
 impl Drop for ManagedDatabaseInner {
     fn drop(&mut self) {
         let driver = &self.driver.driver;
-        let mut database = self.database.lock().unwrap();
-        let method = driver_method!(driver, DatabaseRelease);
-        // TODO(alexandreyc): how should we handle `DatabaseRelease` failing?
-        // See: 
https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
-        unsafe { method(database.deref_mut(), null_mut()) };
+        if let Ok(mut database) = self.database.lock() {
+            let method = driver_method!(driver, DatabaseRelease);
+            // TODO(alexandreyc): how should we handle `DatabaseRelease` 
failing?
+            // See: 
https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
+            unsafe { method(database.deref_mut(), null_mut()) };
+        }
+        // We could still drop here but if the lock is poisoned, we have no
+        // clue what the status is. Since a panic comes from Rust code,
+        // _probably_ the FFI handle is unharmed. But I think it's safer to
+        // leak than to try to release.
     }
 }
 
@@ -637,8 +642,12 @@ impl ManagedDatabase {
         mut connection: adbc_ffi::FFI_AdbcConnection,
     ) -> Result<adbc_ffi::FFI_AdbcConnection> {
         let driver = self.ffi_driver();
-        let mut database = self.inner.database.lock().unwrap();
-
+        let mut database = self.inner.database.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] database is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         // ConnectionInit
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionInit);
@@ -654,7 +663,12 @@ impl Optionable for ManagedDatabase {
 
     fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
         let driver = self.ffi_driver();
-        let database = &mut self.inner.database.lock().unwrap();
+        let mut database = self.inner.database.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] database is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let method = driver_method!(driver, DatabaseGetOptionBytes);
         let populate = |key: *const c_char,
                         value: *mut u8,
@@ -667,7 +681,12 @@ impl Optionable for ManagedDatabase {
 
     fn get_option_double(&self, key: Self::Option) -> Result<f64> {
         let driver = self.ffi_driver();
-        let mut database = self.inner.database.lock().unwrap();
+        let mut database = self.inner.database.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] database is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let key = CString::new(key.as_ref())?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let mut value: f64 = f64::default();
@@ -679,7 +698,12 @@ impl Optionable for ManagedDatabase {
 
     fn get_option_int(&self, key: Self::Option) -> Result<i64> {
         let driver = self.ffi_driver();
-        let mut database = self.inner.database.lock().unwrap();
+        let mut database = self.inner.database.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] database is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let key = CString::new(key.as_ref())?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let mut value: i64 = 0;
@@ -691,7 +715,12 @@ impl Optionable for ManagedDatabase {
 
     fn get_option_string(&self, key: Self::Option) -> Result<String> {
         let driver = self.ffi_driver();
-        let mut database = self.inner.database.lock().unwrap();
+        let mut database = self.inner.database.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] database is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let method = driver_method!(driver, DatabaseGetOption);
         let populate = |key: *const c_char,
                         value: *mut c_char,
@@ -704,7 +733,12 @@ impl Optionable for ManagedDatabase {
 
     fn set_option(&mut self, key: Self::Option, value: OptionValue) -> 
Result<()> {
         let driver = self.ffi_driver();
-        let mut database = self.inner.database.lock().unwrap();
+        let mut database = self.inner.database.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] database is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         set_option_database(
             driver,
             database.deref_mut(),
@@ -765,11 +799,16 @@ struct ManagedConnectionInner {
 impl Drop for ManagedConnectionInner {
     fn drop(&mut self) {
         let driver = &self.database.driver.driver;
-        let mut connection = self.connection.lock().unwrap();
-        let method = driver_method!(driver, ConnectionRelease);
-        // TODO(alexandreyc): how should we handle `ConnectionRelease` failing?
-        // See: 
https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
-        unsafe { method(connection.deref_mut(), null_mut()) };
+        if let Ok(mut connection) = self.connection.lock() {
+            let method = driver_method!(driver, ConnectionRelease);
+            // TODO(alexandreyc): how should we handle `ConnectionRelease` 
failing?
+            // See: 
https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
+            unsafe { method(connection.deref_mut(), null_mut()) };
+        }
+        // We could still drop here but if the lock is poisoned, we have no
+        // clue what the status is. Since a panic comes from Rust code,
+        // _probably_ the FFI handle is unharmed. But I think it's safer to
+        // leak than to try to release.
     }
 }
 
@@ -779,6 +818,36 @@ pub struct ManagedConnection {
     inner: Arc<ManagedConnectionInner>,
 }
 
+struct ConnectionCancelHandle {
+    inner: std::sync::Weak<ManagedConnectionInner>,
+}
+
+impl adbc_core::CancelHandle for ConnectionCancelHandle {
+    fn try_cancel(&self) -> Result<()> {
+        if let Some(inner) = self.inner.upgrade() {
+            if let AdbcVersion::V100 = inner.database.driver.version {
+                return Err(Error::with_message_and_status(
+                    ERR_CANCEL_UNSUPPORTED,
+                    Status::NotImplemented,
+                ));
+            }
+            let driver = &inner.database.driver.driver;
+            let mut connection = inner.connection.lock().map_err(|e| {
+                Error::with_message_and_status(
+                    format!("[Driver Manager] connection is poisoned: {e:?}"),
+                    Status::Internal,
+                )
+            })?;
+            let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
+            let method = driver_method!(driver, ConnectionCancel);
+            let status = unsafe { method(connection.deref_mut(), &mut error) };
+            check_status(status, error)
+        } else {
+            Ok(())
+        }
+    }
+}
+
 impl ManagedConnection {
     fn ffi_driver(&self) -> &adbc_ffi::FFI_AdbcDriver {
         &self.inner.database.driver.driver
@@ -794,7 +863,12 @@ impl Optionable for ManagedConnection {
 
     fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let method = driver_method!(driver, ConnectionGetOptionBytes);
         let populate = |key: *const c_char,
                         value: *mut u8,
@@ -809,7 +883,12 @@ impl Optionable for ManagedConnection {
         let key = CString::new(key.as_ref())?;
         let mut value: f64 = f64::default();
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionGetOptionDouble);
         let status =
@@ -822,7 +901,12 @@ impl Optionable for ManagedConnection {
         let key = CString::new(key.as_ref())?;
         let mut value: i64 = 0;
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionGetOptionInt);
         let status =
@@ -833,7 +917,12 @@ impl Optionable for ManagedConnection {
 
     fn get_option_string(&self, key: Self::Option) -> Result<String> {
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let method = driver_method!(driver, ConnectionGetOption);
         let populate = |key: *const c_char,
                         value: *mut c_char,
@@ -846,7 +935,12 @@ impl Optionable for ManagedConnection {
 
     fn set_option(&mut self, key: Self::Option, value: OptionValue) -> 
Result<()> {
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         set_option_connection(
             driver,
             connection.deref_mut(),
@@ -862,7 +956,12 @@ impl Connection for ManagedConnection {
 
     fn new_statement(&mut self) -> Result<Self::StatementType> {
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut statement = adbc_ffi::FFI_AdbcStatement::default();
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementNew);
@@ -877,24 +976,20 @@ impl Connection for ManagedConnection {
         Ok(Self::StatementType { inner })
     }
 
-    fn cancel(&mut self) -> Result<()> {
-        if let AdbcVersion::V100 = self.driver_version() {
-            return Err(Error::with_message_and_status(
-                ERR_CANCEL_UNSUPPORTED,
-                Status::NotImplemented,
-            ));
-        }
-        let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
-        let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
-        let method = driver_method!(driver, ConnectionCancel);
-        let status = unsafe { method(connection.deref_mut(), &mut error) };
-        check_status(status, error)
+    fn get_cancel_handle(&self) -> Box<dyn adbc_core::CancelHandle> {
+        Box::new(ConnectionCancelHandle {
+            inner: Arc::downgrade(&self.inner),
+        })
     }
 
     fn commit(&mut self) -> Result<()> {
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionCommit);
         let status = unsafe { method(connection.deref_mut(), &mut error) };
@@ -903,7 +998,12 @@ impl Connection for ManagedConnection {
 
     fn rollback(&mut self) -> Result<()> {
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionRollback);
         let status = unsafe { method(connection.deref_mut(), &mut error) };
@@ -922,7 +1022,12 @@ impl Connection for ManagedConnection {
             .map(|c| (c.as_ptr(), c.len()))
             .unwrap_or((null(), 0));
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionGetInfo);
         let status = unsafe {
@@ -978,7 +1083,12 @@ impl Connection for ManagedConnection {
         };
 
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionGetObjects);
         let mut stream = FFI_ArrowArrayStream::empty();
@@ -1026,7 +1136,12 @@ impl Connection for ManagedConnection {
 
         let mut stream = FFI_ArrowArrayStream::empty();
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionGetStatistics);
         let status = unsafe {
@@ -1054,7 +1169,12 @@ impl Connection for ManagedConnection {
         }
         let mut stream = FFI_ArrowArrayStream::empty();
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionGetStatisticNames);
         let status = unsafe { method(connection.deref_mut(), &mut stream, &mut 
error) };
@@ -1079,7 +1199,12 @@ impl Connection for ManagedConnection {
 
         let mut schema = FFI_ArrowSchema::empty();
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionGetTableSchema);
         let status = unsafe {
@@ -1099,7 +1224,12 @@ impl Connection for ManagedConnection {
     fn get_table_types(&self) -> Result<Box<dyn RecordBatchReader + Send + 
'static>> {
         let mut stream = FFI_ArrowArrayStream::empty();
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionGetTableTypes);
         let status = unsafe { method(connection.deref_mut(), &mut stream, &mut 
error) };
@@ -1114,7 +1244,12 @@ impl Connection for ManagedConnection {
     ) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
         let mut stream = FFI_ArrowArrayStream::empty();
         let driver = self.ffi_driver();
-        let mut connection = self.inner.connection.lock().unwrap();
+        let mut connection = self.inner.connection.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] connection is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, ConnectionReadPartition);
         let partition = partition.as_ref();
@@ -1153,10 +1288,45 @@ impl ManagedStatement {
     }
 }
 
+struct StatementCancelHandle {
+    inner: std::sync::Weak<ManagedStatementInner>,
+}
+
+impl adbc_core::CancelHandle for StatementCancelHandle {
+    fn try_cancel(&self) -> Result<()> {
+        if let Some(inner) = self.inner.upgrade() {
+            if let AdbcVersion::V100 = 
inner.connection.database.driver.version {
+                return Err(Error::with_message_and_status(
+                    ERR_CANCEL_UNSUPPORTED,
+                    Status::NotImplemented,
+                ));
+            }
+            let driver = &inner.connection.database.driver.driver;
+            let mut statement = inner.statement.lock().map_err(|e| {
+                Error::with_message_and_status(
+                    format!("[Driver Manager] statement is poisoned: {e:?}"),
+                    Status::Internal,
+                )
+            })?;
+            let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
+            let method = driver_method!(driver, StatementCancel);
+            let status = unsafe { method(statement.deref_mut(), &mut error) };
+            check_status(status, error)
+        } else {
+            Ok(())
+        }
+    }
+}
+
 impl Statement for ManagedStatement {
     fn bind(&mut self, batch: RecordBatch) -> Result<()> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementBind);
         let batch: StructArray = batch.into();
@@ -1168,7 +1338,12 @@ impl Statement for ManagedStatement {
 
     fn bind_stream(&mut self, reader: Box<dyn RecordBatchReader + Send>) -> 
Result<()> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementBindStream);
         let mut stream = FFI_ArrowArrayStream::new(reader);
@@ -1177,24 +1352,20 @@ impl Statement for ManagedStatement {
         Ok(())
     }
 
-    fn cancel(&mut self) -> Result<()> {
-        if let AdbcVersion::V100 = self.driver_version() {
-            return Err(Error::with_message_and_status(
-                ERR_CANCEL_UNSUPPORTED,
-                Status::NotImplemented,
-            ));
-        }
-        let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
-        let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
-        let method = driver_method!(driver, StatementCancel);
-        let status = unsafe { method(statement.deref_mut(), &mut error) };
-        check_status(status, error)
+    fn get_cancel_handle(&self) -> Box<dyn adbc_core::CancelHandle> {
+        Box::new(StatementCancelHandle {
+            inner: Arc::downgrade(&self.inner),
+        })
     }
 
     fn execute(&mut self) -> Result<Box<dyn RecordBatchReader + Send + 
'static>> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementExecuteQuery);
         let mut stream = FFI_ArrowArrayStream::empty();
@@ -1206,7 +1377,12 @@ impl Statement for ManagedStatement {
 
     fn execute_schema(&mut self) -> Result<arrow_schema::Schema> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementExecuteSchema);
         let mut schema = FFI_ArrowSchema::empty();
@@ -1217,7 +1393,12 @@ impl Statement for ManagedStatement {
 
     fn execute_update(&mut self) -> Result<Option<i64>> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementExecuteQuery);
         let mut rows_affected: i64 = -1;
@@ -1235,7 +1416,12 @@ impl Statement for ManagedStatement {
 
     fn execute_partitions(&mut self) -> Result<PartitionedResult> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementExecutePartitions);
         let mut schema = FFI_ArrowSchema::empty();
@@ -1263,7 +1449,12 @@ impl Statement for ManagedStatement {
 
     fn get_parameter_schema(&self) -> Result<arrow_schema::Schema> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementGetParameterSchema);
         let mut schema = FFI_ArrowSchema::empty();
@@ -1274,7 +1465,12 @@ impl Statement for ManagedStatement {
 
     fn prepare(&mut self) -> Result<()> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementPrepare);
         let status = unsafe { method(statement.deref_mut(), &mut error) };
@@ -1285,7 +1481,12 @@ impl Statement for ManagedStatement {
     fn set_sql_query(&mut self, query: impl AsRef<str>) -> Result<()> {
         let query = CString::new(query.as_ref())?;
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementSetSqlQuery);
         let status = unsafe { method(statement.deref_mut(), query.as_ptr(), 
&mut error) };
@@ -1295,7 +1496,12 @@ impl Statement for ManagedStatement {
 
     fn set_substrait_plan(&mut self, plan: impl AsRef<[u8]>) -> Result<()> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementSetSubstraitPlan);
         let plan = plan.as_ref();
@@ -1311,7 +1517,12 @@ impl Optionable for ManagedStatement {
 
     fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let method = driver_method!(driver, StatementGetOptionBytes);
         let populate = |key: *const c_char,
                         value: *mut u8,
@@ -1326,7 +1537,12 @@ impl Optionable for ManagedStatement {
         let key = CString::new(key.as_ref())?;
         let mut value: f64 = f64::default();
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementGetOptionDouble);
         let status = unsafe { method(statement.deref_mut(), key.as_ptr(), &mut 
value, &mut error) };
@@ -1338,7 +1554,12 @@ impl Optionable for ManagedStatement {
         let key = CString::new(key.as_ref())?;
         let mut value: i64 = 0;
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let mut error = adbc_ffi::FFI_AdbcError::with_driver(driver);
         let method = driver_method!(driver, StatementGetOptionInt);
         let status = unsafe { method(statement.deref_mut(), key.as_ptr(), &mut 
value, &mut error) };
@@ -1348,7 +1569,12 @@ impl Optionable for ManagedStatement {
 
     fn get_option_string(&self, key: Self::Option) -> Result<String> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         let method = driver_method!(driver, StatementGetOption);
         let populate = |key: *const c_char,
                         value: *mut c_char,
@@ -1361,7 +1587,12 @@ impl Optionable for ManagedStatement {
 
     fn set_option(&mut self, key: Self::Option, value: OptionValue) -> 
Result<()> {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
+        let mut statement = self.inner.statement.lock().map_err(|e| {
+            Error::with_message_and_status(
+                format!("[Driver Manager] statement is poisoned: {e:?}"),
+                Status::Internal,
+            )
+        })?;
         set_option_statement(
             driver,
             statement.deref_mut(),
@@ -1375,10 +1606,15 @@ impl Optionable for ManagedStatement {
 impl Drop for ManagedStatement {
     fn drop(&mut self) {
         let driver = self.ffi_driver();
-        let mut statement = self.inner.statement.lock().unwrap();
-        let method = driver_method!(driver, StatementRelease);
-        // TODO(alexandreyc): how should we handle `StatementRelease` failing?
-        // See: 
https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
-        unsafe { method(statement.deref_mut(), null_mut()) };
+        if let Ok(mut statement) = self.inner.statement.lock() {
+            let method = driver_method!(driver, StatementRelease);
+            // TODO(alexandreyc): how should we handle `StatementRelease` 
failing?
+            // See: 
https://github.com/apache/arrow-adbc/pull/1742#discussion_r1574388409
+            unsafe { method(statement.deref_mut(), null_mut()) };
+        }
+        // We could still drop here but if the lock is poisoned, we have no
+        // clue what the status is. Since a panic comes from Rust code,
+        // _probably_ the FFI handle is unharmed. But I think it's safer to
+        // leak than to try to release.
     }
 }
diff --git a/rust/driver_manager/tests/driver_manager_sqlite.rs 
b/rust/driver_manager/tests/driver_manager_sqlite.rs
index 0f6d89fa2..a84d8cb84 100644
--- a/rust/driver_manager/tests/driver_manager_sqlite.rs
+++ b/rust/driver_manager/tests/driver_manager_sqlite.rs
@@ -128,9 +128,10 @@ fn test_connection_get_option() {
 fn test_connection_cancel() {
     let mut driver = get_driver();
     let database = get_database(&mut driver);
-    let mut connection = database.new_connection().unwrap();
+    let connection = database.new_connection().unwrap();
 
-    let error = connection.cancel().unwrap_err();
+    let handle = connection.get_cancel_handle();
+    let error = handle.try_cancel().unwrap_err();
     assert_eq!(error.status, Status::NotImplemented);
 }
 
@@ -285,9 +286,10 @@ fn test_statement_cancel() {
     let mut driver = get_driver();
     let database = get_database(&mut driver);
     let mut connection = database.new_connection().unwrap();
-    let mut statement = connection.new_statement().unwrap();
+    let statement = connection.new_statement().unwrap();
 
-    let error = statement.cancel().unwrap_err();
+    let handle = statement.get_cancel_handle();
+    let error = handle.try_cancel().unwrap_err();
     assert_eq!(error.status, Status::NotImplemented);
 }
 
diff --git a/rust/ffi/src/driver_exporter.rs b/rust/ffi/src/driver_exporter.rs
index 429f0caf3..00a29ef1b 100644
--- a/rust/ffi/src/driver_exporter.rs
+++ b/rust/ffi/src/driver_exporter.rs
@@ -61,11 +61,16 @@ impl<DriverType: Driver> ExportedDatabase<DriverType> {
     }
 }
 
+struct InitializedConnection<DriverType: Driver> {
+    connection: ConnectionType<DriverType>,
+    cancel_handle: Box<dyn adbc_core::CancelHandle>,
+}
+
 enum ExportedConnection<DriverType: Driver> {
     /// Pre-init options
     Options(HashMap<OptionConnection, OptionValue>),
     /// Initialized connection
-    Connection(ConnectionType<DriverType>),
+    Connection(InitializedConnection<DriverType>),
 }
 
 impl<DriverType: Driver> ExportedConnection<DriverType> {
@@ -77,13 +82,23 @@ impl<DriverType: Driver> ExportedConnection<DriverType> {
     ) {
         match self {
             Self::Options(options) => (Some(options), None),
-            Self::Connection(connection) => (None, Some(connection)),
+            Self::Connection(connection) => (None, Some(&mut 
connection.connection)),
         }
     }
 
     fn try_connection(&mut self) -> Result<&mut ConnectionType<DriverType>> {
         match self {
-            Self::Connection(connection) => Ok(connection),
+            Self::Connection(connection) => Ok(&mut connection.connection),
+            _ => Err(Error::with_message_and_status(
+                "Connection not initialized",
+                Status::InvalidState,
+            )),
+        }
+    }
+
+    fn try_cancel(&mut self) -> Result<&mut dyn adbc_core::CancelHandle> {
+        match self {
+            Self::Connection(connection) => 
Ok(connection.cancel_handle.as_mut()),
             _ => Err(Error::with_message_and_status(
                 "Connection not initialized",
                 Status::InvalidState,
@@ -92,7 +107,10 @@ impl<DriverType: Driver> ExportedConnection<DriverType> {
     }
 }
 
-struct ExportedStatement<DriverType: Driver>(StatementType<DriverType>);
+struct ExportedStatement<DriverType: Driver>(
+    StatementType<DriverType>,
+    Box<dyn adbc_core::CancelHandle>,
+);
 
 pub trait FFIDriver {
     fn ffi_driver() -> FFI_AdbcDriver;
@@ -828,7 +846,10 @@ unsafe fn connection_set_option_impl<DriverType: Driver, 
Value: Into<OptionValue
             options.insert(key.into(), value.into());
         }
         ExportedConnection::Connection(connection) => {
-            check_err!(connection.set_option(key.into(), value.into()), error);
+            check_err!(
+                connection.connection.set_option(key.into(), value.into()),
+                error
+            );
         }
     }
 
@@ -878,7 +899,11 @@ extern "C" fn connection_init<DriverType: Driver>(
                 )),
             };
             let connection = check_err!(connection, error);
-            *exported_connection = ExportedConnection::Connection(connection);
+            let cancel_handle = connection.get_cancel_handle();
+            *exported_connection = 
ExportedConnection::Connection(InitializedConnection {
+                connection,
+                cancel_handle,
+            });
         } else {
             check_err!(
                 Err(Error::with_message_and_status(
@@ -1224,8 +1249,8 @@ extern "C" fn connection_cancel<DriverType: Driver>(
             unsafe { connection_private_data::<DriverType>(connection) },
             error
         );
-        let connection = check_err!(exported.try_connection(), error);
-        check_err!(connection.cancel(), error);
+        let handle = check_err!(exported.try_cancel(), error);
+        check_err!(handle.try_cancel(), error);
 
         ADBC_STATUS_OK
     })
@@ -1421,8 +1446,12 @@ extern "C" fn statement_new<DriverType: Driver>(
         let inner_connection = 
check_err!(exported_connection.try_connection(), error);
 
         let inner_statement = check_err!(inner_connection.new_statement(), 
error);
+        let cancel_handle = inner_statement.get_cancel_handle();
 
-        let exported = 
Box::new(ExportedStatement::<DriverType>(inner_statement));
+        let exported = Box::new(ExportedStatement::<DriverType>(
+            inner_statement,
+            cancel_handle,
+        ));
         statement.private_data = Box::into_raw(exported) as *mut c_void;
 
         ADBC_STATUS_OK
@@ -1685,9 +1714,8 @@ extern "C" fn statement_cancel<DriverType: Driver>(
             unsafe { statement_private_data::<DriverType>(statement) },
             error
         );
-        let statement = &mut exported.0;
-
-        check_err!(statement.cancel(), error);
+        let cancel_handle = &mut exported.1;
+        check_err!(cancel_handle.try_cancel(), error);
 
         ADBC_STATUS_OK
     })

Reply via email to