fix: a race condition when terminal resumes while signal system is restarting

This commit is contained in:
sxyazi 2025-12-19 18:39:32 +08:00
parent e7cd66370f
commit 5bbc6c1287
No known key found for this signature in database
11 changed files with 54 additions and 41 deletions

2
Cargo.lock generated
View file

@ -4747,6 +4747,7 @@ name = "yazi-binding"
version = "25.9.15" version = "25.9.15"
dependencies = [ dependencies = [
"ansi-to-tui", "ansi-to-tui",
"futures",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"mlua", "mlua",
"paste", "paste",
@ -5087,6 +5088,7 @@ dependencies = [
"tokio", "tokio",
"tokio-util", "tokio-util",
"tracing", "tracing",
"yazi-binding",
"yazi-config", "yazi-config",
"yazi-dds", "yazi-dds",
"yazi-fs", "yazi-fs",

View file

@ -5,6 +5,7 @@ use crossterm::{execute, style::Print};
use hashbrown::HashMap; use hashbrown::HashMap;
use scopeguard::defer; use scopeguard::defer;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use yazi_binding::Permit;
use yazi_config::{YAZI, opener::OpenerRule}; use yazi_config::{YAZI, opener::OpenerRule};
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}}; 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; .await;
let _permit = HIDER.acquire().await.unwrap(); let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
defer!(AppProxy::resume());
AppProxy::stop().await; AppProxy::stop().await;
let new: Vec<_> = Local::regular(&tmp) 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 crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};
use scopeguard::defer; use scopeguard::defer;
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time}; use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
use yazi_binding::Permit;
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::VoidOpt; use yazi_parser::VoidOpt;
use yazi_proxy::{AppProxy, HIDER}; use yazi_proxy::{AppProxy, HIDER};
@ -26,7 +27,7 @@ impl Actor for Inspect {
}; };
tokio::spawn(async move { 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 (tx, mut rx) = mpsc::unbounded_channel();
let buffered = { let buffered = {
@ -37,9 +38,7 @@ impl Actor for Inspect {
task.logs.clone() task.logs.clone()
}; };
defer!(AppProxy::resume());
AppProxy::stop().await; AppProxy::stop().await;
terminal_clear(TTY.writer()).ok(); terminal_clear(TTY.writer()).ok();
TTY.writer().write_all(buffered.as_bytes()).ok(); TTY.writer().write_all(buffered.as_bytes()).ok();
TTY.writer().flush().ok(); TTY.writer().flush().ok();

View file

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

View file

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

@ -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 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()); 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()); return Err("Cannot hide while already hidden".into_lua_err());
} }
let permit = HIDER.acquire().await.unwrap(); let permit = HIDER.acquire().await.unwrap();
AppProxy::stop().await; 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") lua.named_registry_value::<AnyUserData>("HIDE_PERMIT")
})?; })?;

View file

@ -35,14 +35,14 @@ impl Utils {
lua.create_async_function(|lua, ()| async move { 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"); 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()); return Err("Cannot hide while already hidden".into_lua_err());
} }
let permit = HIDER.acquire().await.unwrap(); let permit = HIDER.acquire().await.unwrap();
AppProxy::stop().await; 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") lua.named_registry_value::<AnyUserData>("HIDE_PERMIT")
}) })
} }

View file

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

View file

@ -12,15 +12,16 @@ repository = "https://github.com/sxyazi/yazi"
workspace = true workspace = true
[dependencies] [dependencies]
yazi-config = { path = "../yazi-config", version = "25.9.15" } yazi-binding = { path = "../yazi-binding", version = "25.9.15" }
yazi-dds = { path = "../yazi-dds", version = "25.9.15" } yazi-config = { path = "../yazi-config", version = "25.9.15" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-dds = { path = "../yazi-dds", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
yazi-parser = { path = "../yazi-parser", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" } yazi-parser = { path = "../yazi-parser", version = "25.9.15" }
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" } yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }

View file

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