Romeo Mihalcea
Romeo Mihalcea

Reputation: 10252

Rust/rocket pass variable to endpoints

Not to my preference but I'm forced to write some Rust today so I'm trying to create a Rocket instance with only one endpoint but, on that endpoint I need to access a variable that is being created during main. The variable takes a long time to be instantiated so that's why I do it there.

My problem is that I can;t find a way to pass it safely. Whatever I do, the compiler complaints about thread safety even though the library appears to be thread safe: https://github.com/brave/adblock-rust/pull/130 (commited code is found on my local instance)

This is the error tat I get:

   |
18 | / lazy_static! {
19 | |     static ref rules_engine: Mutex<Vec<Engine>> = Mutex::new(vec![]);
20 | | }
   | |_^ `std::rc::Rc<std::cell::RefCell<lifeguard::CappedCollection<std::vec::Vec<u64>>>>` cannot be sent between threads safely
   | 

...and this is my code:

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

use lazy_static::lazy_static;
use std::sync::Mutex;

use adblock::engine::Engine;
use adblock::lists::FilterFormat;

use rocket::request::{Form, FormError, FormDataError};

lazy_static! {
    static ref rules_engine: Mutex<Vec<Engine>> = Mutex::new(vec![]);
}

fn main() {
    if !Path::new("./rules.txt").exists() {
        println!("rules file does not exist")
    } else {
        println!("loading rules");

        let mut rules = vec![];

        if let Ok(lines) = read_lines("./rules.txt") {
            for line in lines {
                if let Ok(ip) = line {
                    rules.insert(0, ip)
                }
            }

            let eng = Engine::from_rules(&rules, FilterFormat::Standard);
            rules_engine.lock().unwrap().push(eng);
            rocket().launch();
        }
    }
}

#[derive(Debug, FromForm)]
struct FormInput<> {
    #[form(field = "textarea")]
    text_area: String
}

#[post("/", data = "<sink>")]
fn sink(sink: Result<Form<FormInput>, FormError>) -> String {
    match sink {
        Ok(form) => {
            format!("{:?}", &*form)
        }
        Err(FormDataError::Io(_)) => format!("Form input was invalid UTF-8."),
        Err(FormDataError::Malformed(f)) | Err(FormDataError::Parse(_, f)) => {
            format!("Invalid form input: {}", f)
        }
    }
}

fn rocket() -> rocket::Rocket {
    rocket::ignite().mount("/", routes![sink])
}

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
    where P: AsRef<Path>, {
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

Any way of having the eng available inside the sink endpoint method?

Upvotes: 2

Views: 1408

Answers (2)

MYZ
MYZ

Reputation: 359

Rocket makes it really easy to share resources between routes (and also between main or any other thread you might have spawned from main). They call their mechanism state. Check out its documentation here.

To give a short example of how it works: You create your type that you want to share in your application and manage an instance of that type in the instance of rocket that you use for your application. In the guide they give this example:

use std::sync::atomic::AtomicUsize;

struct HitCount {
    count: AtomicUsize
}

rocket::build().manage(HitCount { count: AtomicUsize::new(0) });

In a route then you access the resource like this (again from the guide):

use rocket::State;

#[get("/count")]
fn count(hit_count: &State<HitCount>) -> String {
    let current_count = hit_count.count.load(Ordering::Relaxed);
    format!("Number of visits: {}", current_count)
}

While I learnt rocket I needed to share a struct that contained a String, which is not thread safe per se. That means you need to wrap it into a Mutex before you can manage it with rocket.

Also, as far as I understand, only one resource of any specific type can be shared with manage. But you can just create differently named wrapper types in that case and work around that limitation.

Upvotes: 0

kmdreko
kmdreko

Reputation: 60517

Rc is not thread safe, even behind a mutex. It looks like Rc is used in eng.blocker.pool.pool which is a lifeguard::Pool. So no, the Engine is not thread safe (at least by default).

Fortunately, it appears that the adblock crate has a feature called "object-pooling", which enables that specific functionality. Removing that feature will (hopefully) make it thread safe.

Upvotes: 2

Related Questions