jonhoo / left-right

A lock-free, read-optimized, concurrency primitive.
Apache License 2.0
1.95k stars 94 forks source link

Global Evmap #20

Closed pakipaki2000 closed 5 years ago

pakipaki2000 commented 5 years ago

Hi!

i'd like to use a map in actixweb. What's the best and current method to use an evmap as a global?

Thanks!

novacrazy commented 5 years ago

You should use the actix app state to store the read/write handles to an evmap

Example:

use std::sync::{Arc, Mutex};

use actix::prelude::*;
use actix_web::{server, ws, App, Path, Responder, State};

pub struct AppState {
    read: evmap::ReadHandle<i32, i32>,
    write: Arc<Mutex<evmap::WriteHandle<i32, i32>>>,
}

/// Define http actor
struct Ws;

impl Actor for Ws {
    type Context = ws::WebsocketContext<Self, AppState>; // Note the extra generic parameter
}

/// Handler for ws::Message message
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
    fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
        // Here is where you would access the state
        let some_value = ctx.state().read.get_and(&0, |vs| vs[0]).unwrap();

        match msg {
            ws::Message::Ping(msg) => ctx.pong(&msg),
            ws::Message::Text(text) => ctx.text(text),
            ws::Message::Binary(bin) => ctx.binary(bin),
            _ => (),
        }
    }
}

fn index(info: Path<(String, u32)>, state: State<AppState>) -> impl Responder {
    format!(
        "Hello {}! id:{} value: {}",
        info.0,
        info.1,
        // get entry
        state.read.get_and(&0, |vs| vs[0]).unwrap()
    )
}

fn main() {
    server::new(|| {
        let (read, write) = evmap::new();

        App::with_state(AppState {
            read,
            write: Arc::new(Mutex::new(write)),
        })
        .resource("/{name}/{id}/index.html", |r| r.with(index))
        .resource("/ws/", |r| r.f(|req| ws::start(req, Ws)))
    })
    .bind("127.0.0.1:8080")
    .unwrap()
    .run();
}
pakipaki2000 commented 5 years ago

Thank you so much!

pakipaki2000 commented 5 years ago

Sorry I'm a little confused about one thing, can you help me integrating it into:

server::new(
    || App::new()

        .middleware(middleware::Logger::default())
        .resource(SERVER_WEBSOCKET_ROUTE_PATH, |r| r.method(http::Method::GET).f(ws_index))

        .handler("/", fs::StaticFiles::new(SERVER_HTTP_ROUTE_PATH)
                 .unwrap()
                 .index_file(SERVER_HTTP_ROUTE_DEFAULT_HTML_FILE)))

    .bind(format!("{}:{}", SERVER_IP, SERVER_PORT)).unwrap()
    .start();

I need to have access in ws_index (in the websocket)

novacrazy commented 5 years ago

I've updated the example code to include the standard actix websocket setup and how you'd access the app state there.

I haven't used a regular "with" style function for websockets before, so I'm unsure what the function signature of that is.

pakipaki2000 commented 5 years ago

Man thank you so much, you saved my day :)

novacrazy commented 5 years ago

No problem. Feel free to close the issue if there are no further questions.

pakipaki2000 commented 5 years ago

I comment here again because apparently in your function, the evmap::new() is executed multiple times.

server::new(|| {
let (read, mut write) = evmap::new();
println!("test");

At runtime, the "test" will show 16 times.

Thanks :)

novacrazy commented 5 years ago

Interesting. It seems actix spawns server threads for the number of logical CPUs on your system, evaluating the setup on each. Makes sense, I suppose.

In that case, you'll want to create the evmap and app state outside of the server creation, like so:

#[derive(Clone)]
pub struct AppState {
    read: evmap::ReadHandle<MyHash, MyHash>,
    write: Arc<Mutex<evmap::WriteHandle<MyHash, MyHash>>>,
}

fn main() {
    let (read, write) = evmap::new();

    let app_state = AppState {
        read,
        write: Arc::new(Mutex::new(write)),
    };

    server::new(move || {
        App::with_state(app_state.clone())
            .resource("/{name}/{id}/index.html", |r| r.with(index))
            .resource("/ws/", |r| r.f(|req| ws::start(req, Ws)))
    })
    .bind("127.0.0.1:8080")
    .unwrap()
    .run();
}

Note that any additional state you add to AppState which is shared among all threads should use Arc for keeping a single shared instance.

novacrazy commented 5 years ago

Hmm. It may actually be better to create a whole Actor for the evmap at this point. I'll have to experiment with that.

pakipaki2000 commented 5 years ago

Hmm. It may actually be better to create a whole Actor for the evmap at this point. I'll have to experiment with that.

Thanks so much, i've integrated your code up, it works. What make you say that you need a whole Actor? What is the flaw in the current way?

novacrazy commented 5 years ago

Given #22, you may want to take advantage of the whole Actor lifecycle and event handling stuff, but if you just want to use the evmap as a regular cache in your app, feel free to ignore that comment.

novacrazy commented 5 years ago

So is this particular issue solved for your use case? @pakipaki2000

pakipaki2000 commented 5 years ago

It's solved, you've been so helpful! Thank you.