Search code examples
rustmutex

tokio Mutex deadlocks in rust


for some reasons that one of my mutex causes "deadlock" in the code.

in the code below inside the ability.perform function the hero_clone which is a mutex is deadlocked and after using lock().await method the program hangs forever.

(Im using tokio::sync::mutex because my functions are async)

use teloxide::dispatching::dialogue::{GetChatId, InMemStorage};
use teloxide::dispatching::{HandlerExt, UpdateFilterExt};
use teloxide::dptree::di::Injectable;
use teloxide::prelude::*;
use teloxide::types::*;
use tokio::sync::{Mutex, MutexGuard};

pub type Players = [Arc<Mutex<Player>>; 2];
pub type Heroes = [Arc<Mutex<Hero>>; 1];

pub impl Game {
    pub fn get_current_turn(&mut self) -> Arc<Mutex<Player>> {
        self.players[self.player_turn].clone()
    }
}
pub struct Player {
    pub id: UserId,
    pub heroes: Heroes,

}

#[tokio::main]
async fn main() {
    unsafe { GAMES.as_mut_ptr().write(HashMap::new()); }
    pretty_env_logger::init();
    log::info!("starting up");
    let bot = Bot::env();
    Dispatcher::builder(bot, dptree::entry()
        .branch(Update::filter_message()
            .enter_dialogue::<Message, InMemStorage<State>, State>()
            .branch(dptree::case![State::PlayerTurn(id)].endpoint(player_turn))
            .branch(dptree::case![State::Idle].endpoint(idle))
            .branch(dptree::case![State::StartGame].endpoint(start_game)))
        .branch(Update::filter_callback_query().endpoint(callback_query)))
        .dependencies(dptree::deps![InMemStorage::<State>::new()]).enable_ctrlc_handler().build().dispatch().await;
}
type HandlerResult = Result<(), Box<dyn Error + Send + Sync>>;

async fn callback_query(bot: Bot, callback_query: CallbackQuery) -> HandlerResult {
                 let hero = usize::from_str(iter.next().unwrap_or("0")).unwrap_or(0);
                 let ability = usize::from_str(iter.next().unwrap_or("0")).unwrap_or(0);
                 let chat_id = callback_query.chat_id().unwrap();
                 let game_original = get_game(&chat_id);
                 let game = game_original.clone();
                 let mut game_mut = game.lock().await;
                 let heroes = &current_turn.heroes;
                 let mut hero = heroes.get(hero).unwrap();
                 let hero_clone = hero.clone();
                 let hero_mut = hero.lock().await;
                 let ability = hero_mut.abilities.get(ability).unwrap();
                 let opponent = game_mut.get_next_turn();
                 ability.perform(&bot, game.clone(), opponent, hero_clone).await;
                 game_mut.next_turn(&bot).await;
               
             }
        }
    }
    Ok(())
}

impl Ability {
    async fn perform(&self, bot: &Bot, game: Arc<Mutex<Game>>, target: Arc<Mutex<Player>>, hero: Arc<Mutex<Hero>>) -> bool {
        let mut hero_unlock = hero.lock().await; //Hangs here forever...
        hero_unlock.damage(40);
        hero_unlock.send_message(bot, &game.lock().await.id, "Take the khafang punchoo!".to_string()).await;
        true
    }

I don't really know if its a dumb question or not but forgive me since im new to rust :)

the perform function shouldn't hang on accessing the hero mutex init.


Solution

  • You are trying to lock hero mutex twice.

       // Here you lock mutex first time.
       let hero_mut = hero.lock().await;
       let ability = hero_mut.abilities.get(ability).unwrap();
       let opponent = game_mut.get_next_turn();
       // Here inside perform
       // you try to lock it second time without unlocking first.
       ability.perform(&bot, game.clone(), opponent, hero_clone).await;
    

    Mutexes are often not recursive so they cannot be locked twice even in same thread.