fix: a race condition when terminal resumes while signal system is restarting (#3448)

This commit is contained in:
三咲雅 misaki masa 2025-12-19 18:46:38 +08:00 committed by GitHub
parent e7cd66370f
commit 329c5d28f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 74 additions and 65 deletions

36
Cargo.lock generated
View file

@ -585,9 +585,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.61"
version = "4.5.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992"
checksum = "004eef6b14ce34759aa7de4aea3217e368f463f46a3ed3764ca4b5a4404003b4"
dependencies = [
"clap",
]
@ -3430,9 +3430,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [
"serde_core",
]
@ -3874,9 +3874,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.9.8"
version = "0.9.10+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
dependencies = [
"indexmap",
"serde_core",
@ -3889,33 +3889,33 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.7.3"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.0.4"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.4"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "tracing"
version = "0.1.43"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
@ -3947,9 +3947,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.35"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
@ -4747,6 +4747,7 @@ name = "yazi-binding"
version = "25.9.15"
dependencies = [
"ansi-to-tui",
"futures",
"hashbrown 0.16.1",
"mlua",
"paste",
@ -5075,18 +5076,17 @@ dependencies = [
"anyhow",
"async-priority-channel",
"foldhash 0.2.0",
"futures",
"hashbrown 0.16.1",
"libc",
"lru 0.16.2",
"mlua",
"ordered-float 5.1.0",
"parking_lot",
"scopeguard",
"serde",
"tokio",
"tokio-util",
"tracing",
"yazi-binding",
"yazi-config",
"yazi-dds",
"yazi-fs",

View file

@ -59,8 +59,8 @@ thiserror = "2.0.17"
tokio = { version = "1.48.0", features = [ "full" ] }
tokio-stream = "0.1.17"
tokio-util = "0.7.17"
toml = { version = "0.9.8" }
tracing = { version = "0.1.43", features = [ "max_level_debug", "release_max_level_debug" ] }
toml = { version = "0.9.10" }
tracing = { version = "0.1.44", features = [ "max_level_debug", "release_max_level_debug" ] }
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
typed-path = "0.12.0"
unicode-width = { version = "0.2.0", default-features = false }

View file

@ -5,6 +5,7 @@ use crossterm::{execute, style::Print};
use hashbrown::HashMap;
use scopeguard::defer;
use tokio::io::AsyncWriteExt;
use yazi_binding::Permit;
use yazi_config::{YAZI, opener::OpenerRule};
use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
@ -65,8 +66,7 @@ impl Actor for BulkRename {
)
.await;
let _permit = HIDER.acquire().await.unwrap();
defer!(AppProxy::resume());
let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
AppProxy::stop().await;
let new: Vec<_> = Local::regular(&tmp)

View file

@ -4,6 +4,7 @@ use anyhow::Result;
use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};
use scopeguard::defer;
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
use yazi_binding::Permit;
use yazi_macro::succ;
use yazi_parser::VoidOpt;
use yazi_proxy::{AppProxy, HIDER};
@ -26,7 +27,7 @@ impl Actor for Inspect {
};
tokio::spawn(async move {
let _permit = HIDER.acquire().await.unwrap();
let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
let (tx, mut rx) = mpsc::unbounded_channel();
let buffered = {
@ -37,9 +38,7 @@ impl Actor for Inspect {
task.logs.clone()
};
defer!(AppProxy::resume());
AppProxy::stop().await;
terminal_clear(TTY.writer()).ok();
TTY.writer().write_all(buffered.as_bytes()).ok();
TTY.writer().flush().ok();

View file

@ -24,6 +24,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
# External dependencies
ansi-to-tui = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
mlua = { workspace = true }
paste = { workspace = true }

View file

@ -1,42 +1,51 @@
use std::{mem, ops::Deref};
use futures::{FutureExt, future::BoxFuture};
use mlua::{UserData, prelude::LuaUserDataMethods};
use tokio::sync::SemaphorePermit;
pub type PermitRef<F> = mlua::UserDataRef<Permit<F>>;
pub type PermitRef = mlua::UserDataRef<Permit>;
pub struct Permit<F: FnOnce()> {
pub struct Permit {
inner: Option<SemaphorePermit<'static>>,
destruct: Option<F>,
destruct: Option<BoxFuture<'static, ()>>,
}
impl<F: FnOnce()> Deref for Permit<F> {
impl Deref for Permit {
type Target = Option<SemaphorePermit<'static>>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl<F: FnOnce()> Permit<F> {
pub fn new(inner: SemaphorePermit<'static>, f: F) -> Self {
Self { inner: Some(inner), destruct: Some(f) }
impl Permit {
pub fn new<F>(inner: SemaphorePermit<'static>, f: F) -> Self
where
F: Future<Output = ()> + 'static + Send,
{
Self { inner: Some(inner), destruct: Some(f.boxed()) }
}
fn dropping(&mut self) {
if let Some(f) = self.destruct.take() {
f();
}
if let Some(p) = self.inner.take() {
mem::drop(p);
fn dropping(&mut self) -> impl Future<Output = ()> + 'static {
let inner = self.inner.take();
let destruct = self.destruct.take();
async move {
if let Some(f) = destruct {
f.await;
}
if let Some(p) = inner {
mem::drop(p);
}
}
}
}
impl<F: FnOnce()> Drop for Permit<F> {
fn drop(&mut self) { self.dropping(); }
impl Drop for Permit {
fn drop(&mut self) { tokio::spawn(self.dropping()); }
}
impl<F: FnOnce()> UserData for Permit<F> {
impl UserData for Permit {
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("drop", |_, me, ()| Ok(me.dropping()));
methods.add_async_method_mut("drop", |_, mut me, ()| async move { Ok(me.dropping().await) });
}
}

View file

@ -30,7 +30,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
# External dependencies
clap = { workspace = true }
clap_complete = "4.5.61"
clap_complete = "4.5.62"
clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.10"
vergen-gitcl = { version = "1.0.8", features = [ "build", "rustc" ] }

View file

@ -44,7 +44,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
# External build dependencies
anyhow = { workspace = true }
clap = { workspace = true }
clap_complete = "4.5.61"
clap_complete = "4.5.62"
clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.10"
serde_json = { workspace = true }

View file

@ -44,7 +44,7 @@ impl Signals {
}
});
}
SIGCONT if HIDER.try_acquire().is_ok() => AppProxy::resume(),
SIGCONT if HIDER.try_acquire().is_ok() => _ = tokio::spawn(AppProxy::resume()),
_ => {}
}
true

View file

@ -48,14 +48,14 @@ pub(super) fn hide(lua: &Lua) -> mlua::Result<Value> {
return Err("Cannot call `ui.hide()` during app initialization".into_lua_err());
}
if lua.named_registry_value::<PermitRef<fn()>>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) {
if lua.named_registry_value::<PermitRef>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) {
return Err("Cannot hide while already hidden".into_lua_err());
}
let permit = HIDER.acquire().await.unwrap();
AppProxy::stop().await;
lua.set_named_registry_value("HIDE_PERMIT", Permit::new(permit, AppProxy::resume as fn()))?;
lua.set_named_registry_value("HIDE_PERMIT", Permit::new(permit, AppProxy::resume()))?;
lua.named_registry_value::<AnyUserData>("HIDE_PERMIT")
})?;

View file

@ -35,14 +35,14 @@ impl Utils {
lua.create_async_function(|lua, ()| async move {
deprecate!(lua, "`ya.hide()` is deprecated, use `ui.hide()` instead, in your {}\nSee #2939 for more details: https://github.com/sxyazi/yazi/pull/2939");
if lua.named_registry_value::<PermitRef<fn()>>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) {
if lua.named_registry_value::<PermitRef>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) {
return Err("Cannot hide while already hidden".into_lua_err());
}
let permit = HIDER.acquire().await.unwrap();
AppProxy::stop().await;
lua.set_named_registry_value("HIDE_PERMIT", Permit::new(permit, AppProxy::resume as fn()))?;
lua.set_named_registry_value("HIDE_PERMIT", Permit::new(permit, AppProxy::resume()))?;
lua.named_registry_value::<AnyUserData>("HIDE_PERMIT")
})
}

View file

@ -13,8 +13,10 @@ impl AppProxy {
rx.await.ok();
}
pub fn resume() {
emit!(Call(relay!(app:resume)));
pub async fn resume() {
let (tx, rx) = oneshot::channel::<()>();
emit!(Call(relay!(app:resume).with_any("tx", tx)));
rx.await.ok();
}
pub fn notify(opt: NotifyOpt) {

View file

@ -12,27 +12,26 @@ repository = "https://github.com/sxyazi/yazi"
workspace = true
[dependencies]
yazi-config = { path = "../yazi-config", version = "25.9.15" }
yazi-dds = { path = "../yazi-dds", version = "25.9.15" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-parser = { path = "../yazi-parser", version = "25.9.15" }
yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" }
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
yazi-binding = { path = "../yazi-binding", version = "25.9.15" }
yazi-config = { path = "../yazi-config", version = "25.9.15" }
yazi-dds = { path = "../yazi-dds", version = "25.9.15" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-parser = { path = "../yazi-parser", version = "25.9.15" }
yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" }
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
# External dependencies
anyhow = { workspace = true }
async-priority-channel = "0.2.0"
foldhash = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
lru = { workspace = true }
mlua = { workspace = true }
ordered-float = { workspace = true }
parking_lot = { workspace = true }
scopeguard = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true }
tokio-util = { workspace = true }

View file

@ -1,6 +1,6 @@
use anyhow::{Result, anyhow};
use scopeguard::defer;
use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::mpsc};
use yazi_binding::Permit;
use yazi_proxy::{AppProxy, HIDER};
use super::{ProcessInBg, ProcessInBlock, ProcessInOrphan, ShellOpt};
@ -14,8 +14,7 @@ impl Process {
pub(crate) fn new(ops: &mpsc::UnboundedSender<TaskOp>) -> Self { Self { ops: ops.into() } }
pub(crate) async fn block(&self, task: ProcessInBlock) -> Result<(), ProcessOutBlock> {
let _permit = HIDER.acquire().await.unwrap();
defer!(AppProxy::resume());
let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
AppProxy::stop().await;
let (id, cmd) = (task.id, task.cmd.clone());