From 1fe04ca5c68c10d84045447b909bcb657d626290 Mon Sep 17 00:00:00 2001 From: Soispha Date: Mon, 24 Jul 2023 23:45:44 +0200 Subject: [PATCH] Fix(lua_command::handle): Move lua_command handler to separate thread This makes it possible to have lua code execute commands and receive their output value, without risking a deadlock. --- .../event_types/event/handlers/lua_command.rs | 36 ++------ src/tui_app/app/mod.rs | 90 +++++++++++++------ 2 files changed, 69 insertions(+), 57 deletions(-) diff --git a/src/tui_app/app/events/event_types/event/handlers/lua_command.rs b/src/tui_app/app/events/event_types/event/handlers/lua_command.rs index e22833d..6e7460e 100644 --- a/src/tui_app/app/events/event_types/event/handlers/lua_command.rs +++ b/src/tui_app/app/events/event_types/event/handlers/lua_command.rs @@ -1,38 +1,14 @@ -use std::{sync::Arc, time::Duration}; - -use anyhow::{Context, Result}; -use cli_log::{debug, info}; -use tokio::{task, time::timeout}; +use anyhow::Result; +use cli_log::trace; use crate::app::{events::event_types::EventStatus, App}; +// This function is here mainly to reserve this spot for further processing of the lua command. +// TODO(@Soispha): Move the lua executor thread code from app to this module pub async fn handle(app: &mut App<'_>, command: String) -> Result { - info!("Recieved ci command: `{command}`; executing.."); + trace!("Recieved ci command: `{command}`; executing.."); - let local = task::LocalSet::new(); - - // Run the local task set. - let output = local - .run_until(async move { - let lua = Arc::clone(&app.lua); - debug!("before_handle"); - let c_handle = task::spawn_local(async move { - lua.load(&command) - // FIXME this assumes string output only - .eval_async::() - .await - .with_context(|| format!("Failed to execute: `{command}`")) - }); - debug!("after_handle"); - c_handle - }) - .await; - debug!("after_thread"); - - let output = timeout(Duration::from_secs(10), output) - .await - .context("Failed to join lua command executor")???; - info!("Command returned: `{}`", output); + app.lua_command_tx.send(command).await?; Ok(EventStatus::Ok) } diff --git a/src/tui_app/app/mod.rs b/src/tui_app/app/mod.rs index 65426fb..f70f91f 100644 --- a/src/tui_app/app/mod.rs +++ b/src/tui_app/app/mod.rs @@ -2,22 +2,26 @@ pub mod command_interface; pub mod events; pub mod status; -use std::path::Path; +use std::{path::Path, thread}; use anyhow::{Context, Error, Result}; -use cli_log::info; +use cli_log::{error, info}; use matrix_sdk::Client; use once_cell::sync::OnceCell; use status::{State, Status}; use tokio::{ + runtime::Builder, sync::{mpsc, Mutex}, - task::LocalSet, + task::{self, LocalSet}, }; use tokio_util::sync::CancellationToken; use crate::{ accounts::{Account, AccountsManager}, - app::{command_interface::generate_ci_functions, events::event_types::Event}, + app::{ + command_interface::{generate_ci_functions, Command}, + events::event_types::Event, + }, ui::{central, setup}, }; @@ -39,34 +43,66 @@ pub struct App<'ui> { impl App<'_> { pub fn new() -> Result { - fn set_up_lua(tx: mpsc::Sender) -> mpsc::Sender { + fn set_up_lua(event_call_tx: mpsc::Sender) -> mpsc::Sender { + async fn exec_lua_command( + command: &str, + event_call_tx: mpsc::Sender, + ) -> Result<()> { + let second_event_call_tx = event_call_tx.clone(); + let lua = LUA + .get_or_init(|| { + Mutex::new(generate_ci_functions( + mlua::Lua::new(), + second_event_call_tx, + )) + }) + .lock() + .await; + info!("Recieved command to execute: `{}`", &command); + let output = lua + .load(command) + // FIXME this assumes string output only + .eval_async::() + .await; + match output { + Ok(out) => { + info!("Function `{}` returned: `{}`", command, out); + } + Err(err) => { + error!("Function `{}` returned error: `{}`", command, err); + event_call_tx + .send(Event::CommandEvent( + Command::RaiseError(err.to_string()), + None, + )) + .await?; + } + }; + Ok(()) + } + info!("Setting up Lua context.."); static LUA: OnceCell> = OnceCell::new(); - let (lua_command_tx, mut rx) = mpsc::channel(256); + let (lua_command_tx, mut rx) = mpsc::channel::(256); - let local_set = LocalSet::new(); - local_set.spawn_local(async move { - let lua = LUA - .get_or_init(|| Mutex::new(generate_ci_functions(mlua::Lua::new(), tx))) - .lock() - .await; - info!("Initialized Lua context"); + thread::spawn(move || { + let rt = Builder::new_current_thread().enable_all().build().expect( + "Should always be able to build tokio runtime for lua command handling", + ); + let local = LocalSet::new(); + local.spawn_local(async move { + info!("Lua command handling initialized, waiting for commands.."); + while let Some(command) = rx.recv().await { + info!("Recieved lua command: {}", &command); + let local_event_call_tx = event_call_tx.clone(); - while let Some(command) = rx.recv().await { - info!("Recieved command to execute: `{}`", &command); - let output = lua - .load(&command) - // FIXME this assumes string output only - .eval_async::() - .await - .with_context(|| format!("Failed to execute: `{command}`")); - info!( - "Function `{}` returned: `{}`", - command, - output.unwrap_or("".to_owned()) - ); - } + task::spawn_local(async move { + exec_lua_command(&command, local_event_call_tx).await.expect("This should return all relevent errors by other messages, this should never error"); + }); + } + }); + rt.block_on(local); }); lua_command_tx