[solved] Using non thread-safe code provided by external library

Hello everyone,

Actually a rust beginner, please forgive me if i'm not clear enough or if information are missing.

I'm actually trying to implement a basic web service with actrix but i'm stuck with basic (i guess) threading problems.

I'm using the Rincon library, it provide a structure DatabaseSession :link: which use Rc to hold values.

I read that Rc is not thread safe and can't be used in this context. But i can't change the code provided by the library.

I tried to use Arc to encapsulate my repository, thinking the compiler will create another reference to the DatabaseSession (which use RC) instead of cloning it. But he does not agree with me :slight_smile:.

The question is :

  • How can I get around my problem ?
  • How can I deal with codes that are not thread-safe ?

Thanks,

To expose my problem, I redacted this similar example:

use std::rc::Rc;
use std::string::String;
use std::thread;

// Simplified version of struct provided by an external library - https://github.com/innoave/rincon/blob/e0bde6c78e43668c3cad0b1dd07466dfdd6fa4eb/rincon_session/src/database_session.rs#L36

#[derive(Debug)]
pub struct DatabaseSession {
    database_name: String,
    // Here the Rc which cause the compilation error.
    connector: Rc<String>, // I can't change this RC.
}

impl DatabaseSession {
    fn new() -> Self {
        let example = "test".to_string();
        let inst: DatabaseSession = DatabaseSession {
            database_name: "db".to_string(),
            connector: Rc::new("test".to_string()),
        };
        inst
    }
}

// Simplified version of my implementation of a repository
pub struct Repository {
    session: DatabaseSession,
}

impl Repository {
    fn new(session: DatabaseSession) -> Self {
        return Repository {
            session,
        };
    }
    pub fn greet(&self) {
        println!("Hello");
    }
}

fn main() {
    let session: DatabaseSession = DatabaseSession::new();
    let repo = Repository::new(session);

    thread::spawn(move || {
        repo.greet();
    });
}

Compilation error

error[E0277]: `std::rc::Rc<std::string::String>` cannot be sent between threads safely
  --> src/main.rs:47:5
   |
47 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `std::rc::Rc<std::string::String>` cannot be sent between threads safely
   |
   = help: within `Repository`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::string::String>`
   = note: required because it appears within the type `DatabaseSession`
   = note: required because it appears within the type `Repository`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<Repository>`
   = note: required because it appears within the type `[closure@src/main.rs:47:19: 49:6 repo:std::sync::Arc<Repository>]`
   = note: required by `std::thread::spawn`

error[E0277]: `std::rc::Rc<std::string::String>` cannot be shared between threads safely
  --> src/main.rs:47:5
   |
47 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `std::rc::Rc<std::string::String>` cannot be shared between threads safely
   |
   = help: within `Repository`, the trait `std::marker::Sync` is not implemented for `std::rc::Rc<std::string::String>`
   = note: required because it appears within the type `DatabaseSession`
   = note: required because it appears within the type `Repository`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<Repository>`
   = note: required because it appears within the type `[closure@src/main.rs:47:19: 49:6 repo:std::sync::Arc<Repository>]`
   = note: required by `std::thread::spawn`

error: aborting due to 2 previous errors
Real implementation compilation errors
error[E0277]: `std::rc::Rc<rincon_connector::http::JsonHttpConnector>` cannot be sent between threads safely
  --> src/main.rs:76:16
   |
76 |     let addr = SyncArbiter::start(2, || DbExecutor(repo));
   |                ^^^^^^^^^^^^^^^^^^ `std::rc::Rc<rincon_connector::http::JsonHttpConnector>` cannot be sent between threads safely
   |
   = help: within `rincon_session::database_session::DatabaseSession<rincon_connector::http::JsonHttpConnector>`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<rincon_connector::http::JsonHttpConnector>`
   = note: required because it appears within the type `rincon_session::database_session::DatabaseSession<rincon_connector::http::JsonHttpConnector>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<rincon_session::database_session::DatabaseSession<rincon_connector::http::JsonHttpConnector>>`
   = note: required because it appears within the type `dbstore::db::repository::host::HostRepository`
   = note: required because it appears within the type `[closure@src/main.rs:76:38: 76:57 repo:dbstore::db::repository::host::HostRepository]`
   = note: required by `<actix::sync::SyncArbiter<A>>::start`

error[E0277]: `std::rc::Rc<std::cell::RefCell<tokio_core::reactor::Core>>` cannot be sent between threads safely
  --> src/main.rs:76:16
   |
76 |     let addr = SyncArbiter::start(2, || DbExecutor(repo));
   |                ^^^^^^^^^^^^^^^^^^ `std::rc::Rc<std::cell::RefCell<tokio_core::reactor::Core>>` cannot be sent between threads safely
   |
   = help: within `rincon_session::database_session::DatabaseSession<rincon_connector::http::JsonHttpConnector>`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::cell::RefCell<tokio_core::reactor::Core>>`
   = note: required because it appears within the type `rincon_session::database_session::DatabaseSession<rincon_connector::http::JsonHttpConnector>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<rincon_session::database_session::DatabaseSession<rincon_connector::http::JsonHttpConnector>>`
   = note: required because it appears within the type `dbstore::db::repository::host::HostRepository`
   = note: required because it appears within the type `[closure@src/main.rs:76:38: 76:57 repo:dbstore::db::repository::host::HostRepository]`
   = note: required by `<actix::sync::SyncArbiter<A>>::start`

```

That's a harsh limitation. Usually, if something isn't thread-safe, wrapping it in a Mutex is enough. But Rc is also permanently tied to the thread it has been created on.

  1. File a bug. If the library changed Rc to Arc, it'd make things a lot easier.

  2. If you can have multiple handles to the database, open one handle per thread. The easiest way is with thread-local-object.

  3. If the library allows only one handle, and it uses Rc, then it basically has a hard requirement on being single-threaded forever. In that case the best you can do to work with it is to spawn a dedicated thread for use of the library, and communicate with the thread in and out via channels.

2 Likes

Thanks for your complete answer.

1 Like