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"
dependencies = [
"ansi-to-tui",
"futures",
"hashbrown 0.16.1",
"mlua",
"paste",
@ -5087,6 +5088,7 @@ dependencies = [
"tokio",
"tokio-util",
"tracing",
"yazi-binding",
"yazi-config",
"yazi-dds",
"yazi-fs",

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

@ -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,15 +12,16 @@ 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 }

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());