Rust FFI封装Sqlx Postgres驱动调用tx_query报Tokio Context错误
这个错误的核心原因是**tx_query中调用sqlx的异步query方法时,没有在Tokio Runtime的上下文中执行**——而tx_commit和tx_rollback能正常工作,说明这两个方法的异步调用被正确包裹在了Runtime::block_on中。
具体原因拆解
sqlx的所有异步API(包括事务的query、commit、rollback)都依赖Tokio Runtime提供的上下文来调度异步任务。FFI调用是同步的,必须通过Runtime::block_on将异步操作桥接到同步环境中。如果直接在FFI线程中调用异步方法的.await,当前线程没有Tokio Runtime的上下文,就会抛出This functionality requires a Tokio context错误。
你能正常调用tx_commit和tx_rollback,说明这两个方法的实现里用了block_on包裹异步逻辑,但tx_query遗漏了这一步。
另外还有一种可能:如果你的事务被跨线程访问(C代码在不同线程调用tx_query),而sqlx的PgTransaction默认是!Send的,强行跨线程传递会导致上下文丢失,也会触发类似错误。
解决步骤
统一用
Runtime::block_on包裹所有异步调用
确保tx_query的异步逻辑和tx_commit/tx_rollback一样,被Runtime::block_on包裹。示例代码:#[no_mangle] pub extern "C" fn tx_query(tx_id: usize, query: *const c_char) -> i32 { let rt = RT.lock().unwrap().as_mut().unwrap(); let mut tx_guard = TX_STORE.lock().unwrap(); let tx = match tx_guard.get_mut(tx_id) { Some(tx) => tx, None => return -1, }; let query_str = unsafe { CStr::from_ptr(query).to_str().unwrap() }; // 关键:用block_on包裹query的异步调用 let result = rt.block_on(async { sqlx::query(query_str).execute(tx).await }); match result { Ok(_) => 0, Err(_) => -3, } }处理跨线程访问事务的情况
如果C代码会在多个线程调用FFI接口,需要让PgTransaction变为Send:- 在创建Postgres连接选项时,启用
socket_ipc:let opts = sqlx::postgres::PgConnectOptions::new() .host("localhost") .username("your_user") .password("your_pass") .database("your_db") .enable_socket_ipc(); // 使连接/事务变为Send let conn = sqlx::PgConnection::connect_with(&opts).await.unwrap(); - 确保你的Tokio Runtime是多线程模式(默认创建的Runtime就是多线程,除非手动指定
Builder::new_current_thread())。
- 在创建Postgres连接选项时,启用
确保全局Runtime和事务存储的线程安全
用Mutex或其他线程安全容器存储Runtime和事务,避免多线程访问时出现数据竞争。
内容的提问来源于stack exchange,提问作者Yorgos S.

