Idewave-cli: my pet project

Hi community !

Could you guys please review my pet project and share hints and what can be improved there ?

What I am expecting to got in review: sometimes my runned app freeze, seems like it's a deadlock somewhere in code. I debug hard, but still haven't found the reason. Probably somebody could help me to find what can be the potential deadlock ?

I'm seeing some mixing of sync blocking and async code interaction, that's the most likely issue. You're not permitted to block for long periods in tokio (and most other async runtimes), as you will eventually starve them of threads to run tasks on, leading to a deadlock if those tasks would unblock the blocking code.

I would suggest making your IPC code async first, since I'm guessing that's the most likely to be blocking for long periods and waiting on a response, but in general slapping a tokio spawn_blocking around anything blocking should help.

the reason "why" I did this because my handlers are sync and it's a bit hard for me to implement correct typing to make that handlers async. I mean functions that returned by Processor. Probably you could advice how to implement typing correctly to made sync handlers async ?

With all due respect, it will probably work better for everyone if:

  1. you run into a compile error
  2. you reduce it to a minimal failure case
  3. post that particular error here, rather than a generic "help make this codebase async" request
1 Like

well, this is old subject where I tried to ask for help with this issue. It contains links to sandbox.

Traits, which include Fn*, can't support async directly in Rust right now (in dyn, static uses like impl and bounds are fine). Your best option is to define a custom trait with the async-trait crate.

Otherwise it's start at the bottom and use the async implementations defined by the runtime, find an async version implemented by some other crate, or as I mentioned, wrap the blocking code in spawn_blocking (which dedicates a thread to the work, so it's quite a bit less efficient than natively async code: don't over do it!)

1 Like

Could you tell, is it possible to refactor this code with async-trait ?

#[derive(Debug)]
pub enum State {
    SetEncryption(Vec<u8>),
    SetConnectedToRealm(bool),
}

#[derive(Debug)]
pub struct HandlerInput<'a> {
    pub session: Arc<SyncMutex<Session>>,
    pub data: Option<&'a [u8]>,
    pub data_storage: Arc<SyncMutex<DataStorage>>,
    pub message_income: MessageIncome,
    pub dialog_income: DialogIncome,
}

#[derive(Debug)]
pub enum HandlerOutput {
    Data((u32, Vec<u8>, Vec<u8>)),
    ConnectionRequest(String, u16),
    UpdateState(State),
    Freeze,
    Void,
}

#[derive(Debug)]
pub struct AIManagerInput {
    pub session: Arc<SyncMutex<Session>>,
    pub data_storage: Arc<SyncMutex<DataStorage>>,
    pub output_queue: Arc<SyncMutex<VecDeque<Vec<u8>>>>,
    pub message_income: Option<MessageIncome>,
}

pub type HandlerResult = Result<HandlerOutput, Error>;

pub type HandlerFunction = Box<dyn FnMut(&mut HandlerInput) -> HandlerResult + Send>;

pub type ProcessorResult = Vec<HandlerFunction>;

pub type ProcessorFunction = Box<dyn Fn(&mut HandlerInput) -> ProcessorResult + Send>;

Or it can only be appliable on traits and I will need to look for another way ? It's not clear, how can I define return result, I mean, how to define what async function return ?

Sure, the trait there is Fn and FnMut, so just define your own trait instead:

pub trait Handler {
  fn handle(&mut self, &mut HandlerInput) -> HandlerResult;
}

And then make it async, with the help of async-trait:

#[async_trait]
pub trait Handler {
  async fn handle(&mut self, &mut HandlerInput) -> HandlerResult;
}

Keeping in mind that async-trait will basically just box the result future for you.

As a style note, the convention is to name the boxed version of your trait BoxFoo:

pub type BoxHandler = Box<dyn Handler + Send>;
1 Like

my handler is not a struct instance, it's just a function, how to apply trait on function ?

Well you could make it a (possibly empty) struct and make it an impl, but you can also do a "blanket impl", something like:

#[async_trait]
impl<F> Handler for F
   where F: FnMut(&mut HandlerInput) -> HandlerResult
{
  async fn handle(&mut self, input: &mut HandlerInput) -> HandlerResult {
    self(input).await 
  }
}

probably I am doing smth wrong, but this code not compile:

use crate::types::{HandlerInput, HandlerOutput, HandlerResult};
use crate::types::traits::Processor;

#[async_trait]
pub trait PacketHandler {
    async fn handle(&mut self, input: &mut HandlerInput) -> HandlerResult;
}

pub struct Handler1;
impl PacketHandler for Handler1 {
    async fn handle(&mut self, _: &mut HandlerInput) -> HandlerResult {
        println!("DO SMTH [1]");
        Ok(HandlerOutput::Void)
    }
}

pub struct Handler2;
impl PacketHandler for Handler2 {
    async fn handle(&mut self, _: &mut HandlerInput) -> HandlerResult {
        println!("DO SMTH [2]");
        Ok(HandlerOutput::Void)
    }
}

pub struct TestProcessor;
impl Processor for TestProcessor {
    fn process_input(_: &mut HandlerInput) -> Box<Vec<dyn PacketHandler>> {
        let handlers: Vec<dyn PacketHandler> = match opcode {
            1 => {
                vec![
                    Box::new(Handler1),
                    Box::new(Handler2)
                ]
            },
            2 => {
                vec![
                    Box::new(Handler1),
                    Box::new(Handler2)
                ]
            },
            _ => vec![]
        };

        Box::new(handlers)
    }
}

You didn't post the error, but I'm guessing it's because you're missing the #[async_trait] on the impl. It needs to transform both the trait declaration and the impl.

this helped ! now only 1 error left (not sure where to implement this trait):

29  |     fn process_input(_: &mut HandlerInput) -> Box<Vec<dyn PacketHandler>> {
    |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `(dyn PacketHandler + 'static)`

You probably meant Vec<Box<dyn PacketHandler>>.

seems like that, but now I got another error:

error[E0053]: method `process_input` has an incompatible type for trait
  --> src\client\test.rs:37:5
   |
37 |     fn process_input(_: &mut HandlerInput) -> Vec<Box<dyn PacketHandler>> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait `for<'r, 's> FnMut(&'r mut HandlerInput<'s>) -> Result<HandlerOutput, std::io::Error> + std::marker::Send`, found trait `PacketHandler`   
   |
note: type in trait
  --> src\types\traits.rs:9:5
   |
9  |     fn process_input(input: &mut HandlerInput) -> ProcessorResult;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: expected fn pointer `fn(&mut HandlerInput<'_>) -> Vec<Box<(dyn for<'r, 's> FnMut(&'r mut HandlerInput<'s>) -> Result<HandlerOutput, std::io::Error> + std::marker::Send + 'static)>>`
              found fn pointer `fn(&mut HandlerInput<'_>) -> Vec<Box<(dyn PacketHandler + 'static)>>`

also I added init function to iterate handlers and call each of them and also get error:

fn init() {
    let (input_tx, input_rx) = mpsc::channel::<IncomeMessageType>();

    let mut handlers = TestProcessor::process_input(&mut HandlerInput {
        session: Arc::new(Mutex::new(Session::new())),
        data: None,
        data_storage: Arc::new(Mutex::new(DataStorage::new())),
        dialog_income: DialogIncome::new(input_tx.clone()),
        message_income: MessageIncome::new(input_tx.clone()),
    });

    for mut handler in handlers.iter() {
        &**handler.handle();
    }
}

error:

error[E0599]: no method named `handle` found for reference `&Box<dyn for<'r, 's> FnMut(&'r mut HandlerInput<'s>) -> Result<HandlerOutput, std::io::Error> + std::marker::Send>` in the current scope
  --> src\client\test.rs:71:20
   |
71 |         &**handler.handle();
   |                    ^^^^^^ method not found in `&Box<dyn for<'r, 's> FnMut(&'r mut HandlerInput<'s>) -> Result<HandlerOutput, std::io::Error> + std::marker::Send>`
   |
   = note: `handler` is a function, perhaps you wish to call it
   = help: items from traits can only be used if the trait is implemented and in scope
note: `PacketHandler` defines an item `handle`, perhaps you need to implement it

The first error is telling you that process_input has different return types in the trait and impl: the trait expected a Vec of FnMut and the impl is returning a Vec of your new trait PacketHandler.

The second is complaining about the other side of that, it wants to call a handle method on the results of process_input, but the trait declares that they are FnMut, which doesn't have that method. (It also points out that it looks like you were probably expecting the items to implement the PacketHandler trait, and suggests that you might want to implement it for the type, but you probably don't, here)

You should just need to update the trait to fix both of those.

you mean to fix Processor trait right ?

pub trait Processor {
    fn process_input(input: &mut HandlerInput) -> ProcessorResult;
}

it should return smth like Vec<Box<dyn Handler>> ?

The error says the impl and trait types, they should be the same. Assuming you meant what it said, Vec<Box<dyn PacketHandler>>, then yeah but you probably actually want to change the type ProcessorResult.

1 Like

works, thank you very much !