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.