Внедрение соединения Diesel в промежуточное ПО Iron

При написании своих тестов я хотел бы иметь возможность внедрить соединение в запрос, чтобы я мог обернуть весь тестовый пример в транзакцию (даже если в тестовом примере есть более одного запроса).

Я попытался сделать это, используя BeforeMiddleware, который я могу связать в своих тестовых примерах, чтобы вставить соединение, как таковое:

pub type DatabaseConnection = PooledConnection<ConnectionManager<PgConnection>>;

pub struct DatabaseOverride {
    conn: DatabaseConnection,
}

impl BeforeMiddleware for DatabaseOverride {
    fn before(&self, req: &mut Request) -> IronResult<()> {
        req.extensions_mut().entry::<DatabaseOverride>().or_insert(self.conn);
        Ok(())
    }
}

Однако при попытке сделать это я сталкиваюсь с ошибкой компиляции:

error: the trait bound `std::rc::Rc<diesel::pg::connection::raw::RawConnection>: std::marker::Sync` is not satisfied [E0277]
impl BeforeMiddleware for DatabaseOverride {
     ^~~~~~~~~~~~~~~~
help: run `rustc --explain E0277` to see a detailed explanation
note: `std::rc::Rc<diesel::pg::connection::raw::RawConnection>` cannot be shared between threads safely
note: required because it appears within the type `diesel::pg::PgConnection`
note: required because it appears within the type `r2d2::Conn<diesel::pg::PgConnection>`
note: required because it appears within the type `std::option::Option<r2d2::Conn<diesel::pg::PgConnection>>`
note: required because it appears within the type `r2d2::PooledConnection<r2d2_diesel::ConnectionManager<diesel::pg::PgConnection>>`
note: required because it appears within the type `utility::db::DatabaseOverride`
note: required by `iron::BeforeMiddleware`

error: the trait bound `std::cell::Cell<i32>: std::marker::Sync` is not satisfied [E0277]
impl BeforeMiddleware for DatabaseOverride {
     ^~~~~~~~~~~~~~~~
help: run `rustc --explain E0277` to see a detailed explanation
note: `std::cell::Cell<i32>` cannot be shared between threads safely
note: required because it appears within the type `diesel::pg::PgConnection`
note: required because it appears within the type `r2d2::Conn<diesel::pg::PgConnection>`
note: required because it appears within the type `std::option::Option<r2d2::Conn<diesel::pg::PgConnection>>`
note: required because it appears within the type `r2d2::PooledConnection<r2d2_diesel::ConnectionManager<diesel::pg::PgConnection>>`
note: required because it appears within the type `utility::db::DatabaseOverride`
note: required by `iron::BeforeMiddleware`

Есть ли способ обойти это с соединениями дизеля? Я нашел несколько примеров на Github, чтобы сделать это с помощью ящика pg, но я хотел бы продолжать использовать дизель.


person NeuroXc    schedule 23.06.2016    source источник
comment
Я могу ошибаться (я не рядом с банкоматом среды Rust), но разве вам не нужно реализовывать трейт typemap или что-то подобное, чтобы иметь возможность хранить его в расширениях запроса?   -  person Simon Whitehead    schedule 24.06.2016
comment
Да, у меня есть один реализованный (я не публиковал его здесь для краткости). Проблема здесь в том, что я не могу инициализировать BeforeMiddleware с diesel::pg::PgConnection, потому что PgConnection не реализует Sync. Я надеялся, что кто-то знает обходной путь для этого ограничения.   -  person NeuroXc    schedule 24.06.2016
comment
Сам я не использовал diesel, я не уверен. Однако тот факт, что он использует любой из типов Cell или даже несинхронизированный интеллектуальный указатель, такой как Rc, приведет к тому, что он автоматически не сможет реализовать Sync.   -  person Simon Whitehead    schedule 24.06.2016


Ответы (2)


Этот ответ, безусловно, решит проблему, но он не оптимален. Как уже упоминалось, вы не можете совместно использовать одно соединение, поскольку оно не является потокобезопасным. Однако, несмотря на то, что его обертка в Mutex делает его потокобезопасным, это заставит все потоки сервера использовать одно соединение. Вместо этого вы хотите использовать пул соединений.

Вы можете сделать это с помощью r2d2 и r2d2-diesel. Это позволит установить несколько соединений по мере необходимости и повторно использовать их, когда это возможно, в потокобезопасном режиме.

person sgrif    schedule 17.08.2016

Поскольку у меня недостаточно кода, чтобы воспроизвести вашу проблему, я сделал это:

use std::cell::Cell;

trait Middleware: Sync {}

struct Unsharable(Cell<bool>);

impl Middleware for Unsharable {}

fn main() {}

который имеет ту же ошибку:

error: the trait bound `std::cell::Cell<bool>: std::marker::Sync` is not satisfied [E0277]
impl Middleware for Unsharable {}
     ^~~~~~~~~~
help: run `rustc --explain E0277` to see a detailed explanation
note: `std::cell::Cell<bool>` cannot be shared between threads safely
note: required because it appears within the type `Unsharable`
note: required by `Middleware`

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

use std::sync::Mutex;

struct Sharable(Mutex<Unsharable>);

impl Middleware for Sharable {}

Обратите внимание, что Rust сделал для вас очень хорошую вещь: он предотвратил использование типа, который небезопасен для вызова в нескольких потоках.


При написании своих тестов я хотел бы иметь возможность внедрить соединение в запрос, чтобы я мог обернуть весь тестовый пример в транзакцию (даже если в тестовом примере есть более одного запроса).

Я бы предположил, что, возможно, архитектурные изменения были бы еще лучше. Отделите домены «веб-фреймворка» от вашей «базы данных». Авторы Развитие объектно-ориентированного программного обеспечения под руководством Тесты (рекомендуемая книга) поддерживают этот стиль.

Разделите свой код так, чтобы был метод, который просто принимает какой-то тип, который может начать/завершить транзакцию, написать туда интересные вещи и тщательно их протестировать. Затем на веб-слое достаточно связующего кода, чтобы создать объект транзакции, а затем вызвать следующий уровень вниз.

person Shepmaster    schedule 24.06.2016