Fix deadlock in stdio.format calls in tostring metamethod (#288)

This commit is contained in:
dai 2025-03-24 19:34:51 +01:00 committed by GitHub
parent 822dd19393
commit dc08b91314
Signed by: DevComp
GPG key ID: B5690EEEBB952194
4 changed files with 27 additions and 7 deletions

1
Cargo.lock generated
View file

@ -1742,6 +1742,7 @@ dependencies = [
"dunce", "dunce",
"mlua", "mlua",
"once_cell", "once_cell",
"parking_lot",
"path-clean", "path-clean",
"pathdiff", "pathdiff",
"semver 1.0.23", "semver 1.0.23",

View file

@ -22,4 +22,5 @@ dunce = "1.0"
once_cell = "1.17" once_cell = "1.17"
path-clean = "1.0" path-clean = "1.0"
pathdiff = "0.2" pathdiff = "0.2"
parking_lot = "0.12.3"
semver = "1.0" semver = "1.0"

View file

@ -1,11 +1,9 @@
use std::{ use std::{collections::HashSet, sync::Arc};
collections::HashSet,
sync::{Arc, Mutex},
};
use console::{colors_enabled as get_colors_enabled, set_colors_enabled}; use console::{colors_enabled as get_colors_enabled, set_colors_enabled};
use mlua::prelude::*; use mlua::prelude::*;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use parking_lot::ReentrantMutex;
mod basic; mod basic;
mod config; mod config;
@ -20,7 +18,7 @@ pub use self::config::ValueFormatConfig;
// NOTE: Since the setting for colors being enabled is global, // NOTE: Since the setting for colors being enabled is global,
// and these functions may be called in parallel, we use this global // and these functions may be called in parallel, we use this global
// lock to make sure that we don't mess up the colors for other threads. // lock to make sure that we don't mess up the colors for other threads.
static COLORS_LOCK: Lazy<Arc<Mutex<()>>> = Lazy::new(|| Arc::new(Mutex::new(()))); static COLORS_LOCK: Lazy<Arc<ReentrantMutex<()>>> = Lazy::new(|| Arc::new(ReentrantMutex::new(())));
/** /**
Formats a Lua value into a pretty string using the given config. Formats a Lua value into a pretty string using the given config.
@ -28,7 +26,7 @@ static COLORS_LOCK: Lazy<Arc<Mutex<()>>> = Lazy::new(|| Arc::new(Mutex::new(()))
#[must_use] #[must_use]
#[allow(clippy::missing_panics_doc)] #[allow(clippy::missing_panics_doc)]
pub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> String { pub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> String {
let _guard = COLORS_LOCK.lock().unwrap(); let _guard = COLORS_LOCK.lock();
let were_colors_enabled = get_colors_enabled(); let were_colors_enabled = get_colors_enabled();
set_colors_enabled(were_colors_enabled && config.colors_enabled); set_colors_enabled(were_colors_enabled && config.colors_enabled);
@ -48,7 +46,7 @@ pub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> Stri
#[must_use] #[must_use]
#[allow(clippy::missing_panics_doc)] #[allow(clippy::missing_panics_doc)]
pub fn pretty_format_multi_value(values: &LuaMultiValue, config: &ValueFormatConfig) -> String { pub fn pretty_format_multi_value(values: &LuaMultiValue, config: &ValueFormatConfig) -> String {
let _guard = COLORS_LOCK.lock().unwrap(); let _guard = COLORS_LOCK.lock();
let were_colors_enabled = get_colors_enabled(); let were_colors_enabled = get_colors_enabled();
set_colors_enabled(were_colors_enabled && config.colors_enabled); set_colors_enabled(were_colors_enabled && config.colors_enabled);

View file

@ -122,3 +122,23 @@ stdio.ewrite(typeof(errorMessage))
assertContains("Should format errors similarly to userdata", stdio.format(errorMessage), "<LuaErr") assertContains("Should format errors similarly to userdata", stdio.format(errorMessage), "<LuaErr")
assertContains("Should format errors with stack begins", stdio.format(errorMessage), "Stack Begin") assertContains("Should format errors with stack begins", stdio.format(errorMessage), "Stack Begin")
assertContains("Should format errors with stack ends", stdio.format(errorMessage), "Stack End") assertContains("Should format errors with stack ends", stdio.format(errorMessage), "Stack End")
-- Check that calling stdio.format in a __tostring metamethod by print doesn't cause a deadlock
local inner = {}
setmetatable(inner, {
__tostring = function()
return stdio.format(5)
end,
})
print(inner)
local outer = {}
setmetatable(outer, {
__tostring = function()
return stdio.format(inner)
end,
})
print(outer)