LeonHartley / Coerce-rs

Actor runtime and distributed systems framework for Rust
695 stars 21 forks source link

Production. Actix. #19

Open uwejan opened 1 year ago

uwejan commented 1 year ago

Hey there, I am coming from actix actors background, Suffering from some limitations, and I on search for an alternative or an easy convert.

I am going to state down my use case, and I wonder if it will be feasible to make the convert upon your answers. It is also good to know if the crate is being used on production.

  1. Websocket actor, handle read/write. With actix simple as ctx.addStream(), then implement the message types, for write add write part of the stream to the sate of the actor, and calling when needed.
  2. Db actor, handle db operations. here comes majore suffering with async/await/blocking thread ..etc, will share a snippets at the end.
  3. Also using tauri, so the runtime enviroment has to be shared, where as had a blummer back in while to figure actix runtime fix to make it work, which is still not compelling.
  4. some cpu intensive actors dealing with file system on seperate threads.
  5. back to 4 where as cross actor comunication through recipients or actor refs.

Not going to mention supervisoring as I already read that it is there. Making the move to an already crate or using pure rust / tokio channels is the way to go for this project of mine, but knwoing a crate already has the potentials to solve the issues am facing will just make things easier, It will come at the end how much time the conversion of the project will take. So your answers are really important.

Thank you.

Blocking thread. // lack of async/await support. I also tried atomicResponse but in that case I losse access to self, and being static issues. Not trying to figure out a fix, as moving forward async/await is the future to go.

impl Handler<DbOperations> for McActor {
    type Result = String;
    //type Result = ResponseFuture<String>;
    fn handle(&mut self, msg: DbOperations, ctx: &mut Self::Context) -> Self::Result {
        return match msg {
             DbOperations::Get { key } => {
               block_on(async {
                    sqlx::query_as::<_, DbOperationValues>(select_query)
                        .bind(key)
                        .fetch_one(self.db.as_ref().unwrap())
                        .await
                        .unwrap()
                }).value

            }
            DbOperations::Set { key, value } => {   
                block_on(async {
                    sqlx::query_as::<_, DbOperationValues>(insert_query)
                        .bind(key)
                        .bind(value)
                        .fetch_one(self.db.as_ref().unwrap())
                        .await
                        .unwrap()
                }).value
            }
        };
    }
}
uwejan commented 1 year ago

@LeonHartley Could ou take a look please?

uwejan commented 1 year ago

For the question parent to child comunication and viceversa. I am using the parent system to create supervisoed actor, which is allowing me to call child from parent, and parent from child. I am noticing once a certain child is terminated, is effecting parents scheduled message. I.e. parent has a scheduled messafe which basically prints childrens, but once one child is terminated. Parents schduled message stopes completley. Perhaps am missing a concept here or there, thus i appreciate you taking a look.

RedKinda commented 1 year ago

Looking at your code snippet, why sqlx operations are async, and the handle function should also be async, not sure what the goal of adding block_on and async again is. Either way, I think database operations should not be executed from within an actor but rather a separate (global?) handler, because databases are able to handle multiple operations going on at the same time, while actors are not.

As for the blocking issue, make sure to use .notify instead of .send wherever you dont need an answer, otherwise you might create a deadlock (children sends to parent, and then parent sends to the same child, resulting in a deadlock because both are waiting for the other one to answer).

uwejan commented 1 year ago

DB code you seen, is not related to coerce, it was just to show it was done using actix. And why am converting my code to this crate. You are right about deadlocks. However I am still figuring out, how to do internal communications, parent -> child, and viceversa. I am on local so doing anything like clustering is irrelevent.

gembin commented 1 year ago

This should work? (not tested)

impl Handler<DbOperations> for McActor {
    type Result = ResponseActFuture<Self, Result<String, DatabaseError>>;

    fn handle(&mut self, msg: DbOperations, _ctx: &mut Self::Context) -> Self::Result {
        Box::pin(
            // ...
            async move {
               sqlx::query_as::<_, DbOperationValues>(select_query)
                        .bind(key)
                        .fetch_one(self.db.as_ref().unwrap())
                }
                .into_actor(self)
                .map(|res, _actor, _ctx| res)
            //...
        )
    }
}