Rocket: Mutable reference to State<T>

Created on 24 Jul 2017  路  9Comments  路  Source: SergioBenitez/Rocket

Hello,

Is it possible to have mutable reference to State<T>?
E.g., I have following request handler:

#[get("/")]
fn index(x: State<X>)

And inside that handler I'd like to pass x to another function as &mut x is it possible?
If not then what should I do in this situation? Is there a workaround?

question

Most helpful comment

You need to use inner mutability via a Mutex or RwLock

All 9 comments

You need to use inner mutability via a Mutex or RwLock

To expand on @belst's response: you cannot obtain an &mut T reference from a State<T> as doing so would be racy. This is because requests are handled concurrently, so allowing this would allow two &mut T references to the same T to be active concurrently.

To get access to a mutable T, you need to use some form of internal mutability. Among others, these include the types in sync.

Could anyone show some code for the question?

@dofork You can manage and use a Mutex with something like this:

use std::sync::Mutex;

struct SharedData { numbers: Mutex<Vec<i32>> };

#[get("/<number>")]
fn add_number(number: i32, shared: State<SharedData>) -> String {
    let mut lock = shared.lock().expect("lock shared data");
    lock.numbers.push(number);
    format!("There are now {} numbers", lock.numbers.len())
}

fn main() {
    rocket::ignite()
        //...
        .manage(SharedData { numbers: Mutex::new(vec![5]) })
        .launch();
}

Using an RwLock would work in a similar way. RwLock and Mutex might be too slow if you will have a lot of concurrent operations on the same shared data, however.

@dofork You can manage and use a Mutex with something like this:

use std::sync::Mutex;

struct SharedData { numbers: Mutex<Vec<i32>> };

#[get("/<number>")]
fn add_number(number: i32, shared: State<SharedData>) -> String {
    let lock = shared.lock().expect("lock shared data");
    lock.numbers.push(number);
    format!("There are now {} numbers", lock.numbers.len())
}

fn main() {
    rocket::ignite()
        //...
        .manage(SharedData { numbers: Mutex::new(vec![5]) })
        .launch();
}

Using an RwLock would work in a similar way. RwLock and Mutex might be too slow if you will have a lot of concurrent operations on the same shared data, however.

It seems that when you push a number with lock.numbers.push(number), it has to be mutable, but rocket doesn't allow that. Am I missing something here?

Am I missing something here?

Probably a mut between let and lock: let mut lock = shared.lock().expect("lock shared data");

If that works, I will go back and edit that example.

@emryx26
Mutex and RwLock locks acquired by their respective lock methods have to be mutable if you want to push an item.

this is my working example:

// src/cache.rs

use std::collections::HashMap;
use std::sync::{RwLock, RwLockReadGuard};
use std::sync::atomic::{AtomicI32, AtomicU32};
use std::sync::atomic::Ordering::Relaxed as ordering;
use super::models::Hook;

#[derive(Hash, Eq, PartialEq, Debug)]
pub struct LogPage {
    dt: u32,
    sid: i32,
    pn: i32,
}

impl LogPage {
    pub fn new(d: &u32, s: &i32, p: &i32) -> LogPage {
        LogPage { dt: *d, sid: *s, pn: *p }
    }
}

#[derive(Serialize)]
pub struct Context {
    items: Vec<Hook>,
    page: i32,
    total_page: i32
}

impl Context {
    pub fn new(v: &Vec<Hook>,  p: &i32, tp: &i32 ) -> Context {
        let vec = v.to_vec();
        Context{ items: vec, page: *p, total_page: *tp }
    }
}

pub struct Cache {
    date: AtomicU32,
    counter: AtomicI32,
    map: RwLock<HashMap< LogPage, Context >>
}

impl Cache {
    // init
    pub fn new() -> Cache {
        Cache {
            date: AtomicU32::new(0),
            counter: AtomicI32::new(0),
            map: RwLock::new(HashMap::new())
        }
    }

    // getter
    pub fn date(&self) -> u32 {
        self.date.load(ordering)
    } 

    pub fn counter(&self) -> i32 {
        self.counter.load(ordering)
    } 

    pub fn map(&self) -> RwLockReadGuard<HashMap< LogPage, Context >> {
        self.map.read().unwrap()
    }

    // set values
    pub fn set_date(&self, v: &u32) {
        self.date.store(*v, ordering);
    }

    pub fn set_counter(&self, v: &i32) {
        self.counter.store(*v, ordering);
    }

    pub fn fetch_inc_counter(&self) -> i32 {
        self.counter.fetch_add(1, ordering)
    }

    pub fn map_mut(&self, k: LogPage, v: Context) {
        let mut lock = self.map.write()
            .expect("locking cache to write");
        lock.insert(k, v);
    }
}

then in your route function:

#[get("/logs?<p>")]
pub fn get_logs(
    conn: DbConn,
    p: Option<i32>,
    c: State<Cache>,
) -> Template {
    let pn = p.map(|p| std::cmp::max(p,1)).unwrap_or(1);
    let log_page = LogPage::new(&c.date(), &c.counter(), &pn);

    if let Some(v) = c.map().get(&log_page) {
        info!("return cached context");
        return Template::render("index", &v);
    }

    info!("no cache, getting from db");
    let (items, tot) = Hook::all(&*conn, pn);

    // insert
    info!("caching new context");
    let cached = Context::new(&items, &pn, &tot);
    c.map_mut(log_page, cached);

    let context = Context::new(&items, &pn, &tot);
    Template::render("index", &context)
}

@dofork You can manage and use a Mutex with something like this:

use std::sync::Mutex;

struct SharedData { numbers: Mutex<Vec<i32>> };

#[get("/<number>")]
fn add_number(number: i32, shared: State<SharedData>) -> String {
    let mut lock = shared.lock().expect("lock shared data");
    lock.numbers.push(number);
    format!("There are now {} numbers", lock.numbers.len())
}

fn main() {
    rocket::ignite()
        //...
        .manage(SharedData { numbers: Mutex::new(vec![5]) })
        .launch();
}

Using an RwLock would work in a similar way. RwLock and Mutex might be too slow if you will have a lot of concurrent operations on the same shared data, however.

Modified to compiling+working:

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

use rocket::State;
use std::sync::Mutex;

struct SharedData {
    numbers: Mutex<Vec<i32>>,
}

#[get("/<number>")]
fn add_number(number: i32, shared: State<SharedData>) -> String {
    let shared_data: &SharedData = shared.inner();
    shared_data.numbers.lock().unwrap().push(number);
    format!(
        "There are now {} numbers",
        shared_data.numbers.lock().unwrap().len()
    )
}

fn main() {
    rocket::ignite()
        .mount("/", routes![add_number])
        .manage(SharedData {
            numbers: Mutex::new(vec![5]),
        })
        .launch();
}

I wasn't able to move the vector out of the struct, since its not implementing the copy trait. Wasn't able to resolve that, but directly accessing it works for now ;) Thanks

EDIT:

Here's another slightly modified example, having a second thread modify the numbers vector every second:

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

use rocket::State;
use std::sync::{Arc, Mutex};
use std::thread::{sleep, spawn};
use std::time::Duration;

struct SharedData {
    numbers: Arc<Mutex<Vec<i32>>>,
}

#[get("/<number>")]
fn add_number(number: i32, shared: State<SharedData>) -> String {
    let shared_data: &SharedData = shared.inner();
    shared_data.numbers.lock().unwrap().push(number);
    format!(
        "There are now {} numbers",
        shared_data.numbers.lock().unwrap().len()
    )
}

fn main() {
    let numbers = Arc::new(Mutex::new(vec![5]));

    let numbers_arc = Arc::clone(&numbers);
    let _ = spawn(move || loop {
        sleep(Duration::new(1, 0));
        numbers_arc.lock().unwrap().push(9999);
        println!(
            "There are now {} numbers",
            numbers_arc.lock().unwrap().len()
        )
    });

    rocket::ignite()
        .mount("/", routes![add_number])
        .manage(SharedData { numbers: numbers })
        .launch();
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

haheute picture haheute  路  4Comments

sphinxc0re picture sphinxc0re  路  3Comments

Perseus101 picture Perseus101  路  4Comments

PSeitz picture PSeitz  路  3Comments

kitsuneninetails picture kitsuneninetails  路  4Comments