Компилятор говорит, что данные нельзя безопасно разделить между потоками, даже если данные обернуты в Mutex - PullRequest
0 голосов
/ 14 октября 2018

Я использую Rocket с State, который он передает HTTP-запросам.Эта структура содержит Mutex<DatastoreInstance>, который предоставляет доступ к базе данных SQLite и заблокирован мьютексом, чтобы сделать чтение и запись безопасными.

pub struct DatastoreInstance {
    conn: Connection,
}

Когда структура DatastoreInstance выглядела так, только с SQLiteВ связи с этим все работало нормально, но я также хотел добавить объект транзакции в эту структуру:

pub struct DatastoreInstance {
    conn: Connection,
    events_transaction: Transaction,
}

Это не скомпилировалось, поскольку объект Transaction должен ссылаться на объект Connection, который должен иметьвремя жизни, о котором оно знает.Используемые мной объекты Connection и Transaction в rusqlite определены следующим образом:

pub struct Connection {
    db: RefCell<InnerConnection>,
    cache: StatementCache,
    path: Option<PathBuf>,
}

pub struct Transaction<'conn> {
    conn: &'conn Connection,
    drop_behavior: DropBehavior,
}

Чтобы решить проблемы времени жизни, мне пришлось добавить эти параметры времени жизни, чтобы он работал:

pub struct DatastoreInstance<'a> {
    conn: Connection,
    events_transaction: Transaction<'a>,
}

Это был результат, который должен был работать в соответствии с моим пониманием времени жизни и мьютексов, но теперь я получаю ошибку компилятора, говорящую мне:

`std::cell::RefCell<lru_cache::LruCache<std::string::String, rusqlite::raw_statement::RawStatement>>` cannot be shared between threads safely
    |                                                                                                            
    = help: within `rusqlite::Connection`, the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<lru_cache::LruCache<std::string::String, rusqlite::raw_statement::RawStatement>>`
    = note: required because it appears within the type `rusqlite::cache::StatementCache`                        
    = note: required because it appears within the type `rusqlite::Connection`                                   
    = note: required because of the requirements on the impl of `std::marker::Send` for `&rusqlite::Connection`  
    = note: required because it appears within the type `datastore::DatastoreInstance<'_>`                       
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<datastore::DatastoreInstance<'_>>`
    = note: required because it appears within the type `endpoints::ServerState<'_>`                             
    = note: required by `rocket::State`

Согласно моему пониманию мьютексов,этот код должен быть действительным, потому что вся структура DatastoreInstance обернута в Mutex, что должно гарантировать, что только один поток ссылается на этот объект одновременно.

Чего мне не хватает?

Почему компилятор не находит RefCell безопасным после того, как находится в пределах Connection, на который ссылается Transaction, а не только в Connection?

Должен ли яплохо понимаешь, как работают мьютексы?Мои жизни недействительны и как-то нарушают безопасность чтения / записи?Является ли дизайн с наличием Connection и Transaction в одной структуре плохим дизайном, который нарушает безопасность чтения / записи?Нужно ли каким-то образом перепроектировать мои структуры данных, чтобы сделать это безопасным?Или я просто упускаю что-то очень очевидное?

1 Ответ

0 голосов
/ 14 октября 2018

A Mutex равно Send или Sync , если само значение, которое оно содержит, Send:

impl<T: ?Sized + Send> Send for Mutex<T>    
impl<T: ?Sized + Send> Sync for Mutex<T>

A &T равно Send когда T равно Sync:

impl<'a, T> Send for &'a T
where
    T: Sync + ?Sized, 

А RefCell никогда не Sync

impl<T> !Sync for RefCell<T>
where
    T: ?Sized, 

Как говорится в сообщении об ошибке, ваша транзакция содержит ссылку на RefCell.Неважно, что существует мьютекс, по своей природе он небезопасен для совместного использования его между потоками.Простое воспроизведение:

use std::{cell::RefCell, sync::Mutex};

struct Connection(RefCell<i32>);
struct Transaction<'a>(&'a Connection);

fn is_send<T: Send>(_: T) {}

fn main() {
    let c = Connection(RefCell::new(42));
    let t = Transaction(&c);
    let m = Mutex::new(t);

    is_send(m);
}
error[E0277]: `std::cell::RefCell<i32>` cannot be shared between threads safely
  --> src/main.rs:13:5
   |
13 |     is_send(m);
   |     ^^^^^^^ `std::cell::RefCell<i32>` cannot be shared between threads safely
   |
   = help: within `Connection`, the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<i32>`
   = note: required because it appears within the type `Connection`
   = note: required because of the requirements on the impl of `std::marker::Send` for `&Connection`
   = note: required because it appears within the type `Transaction<'_>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<Transaction<'_>>`
note: required by `is_send`
  --> src/main.rs:6:1
   |
6  | fn is_send<T: Send>(_: T) {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^

Почему компилятор не находит RefCell более безопасным после того, как находится внутри Connection, на который ссылается Transaction, а не только внутриConnection?

RefCell в порядке, это ссылка на RefCell, которая не является.

Является лидизайн с Connection и Transaction в одной структуре плохой дизайн [...] Нужно ли мне перепроектировать мои структуры данных

Да.

...