fix(single-instance): Yield unix listener in macos (#3466)

* Use tokio UnixListener so the task can yield and release the thread

* use standard unix listener, convert to non blocking and cast into tokio yielding one

* Create fix-yield-single-instance-macos.md

* Update .changes/fix-yield-single-instance-macos.md

Co-authored-by: Fabian-Lars <30730186+FabianLars@users.noreply.github.com>

* use net feature in Tokio dependency

* Use tokio UnixListener::bind, inside the tokio task

---------

Co-authored-by: Fabian-Lars <30730186+FabianLars@users.noreply.github.com>
This commit is contained in:
Bajoca
2026-06-29 11:00:59 +02:00
committed by GitHub
parent 41f6274270
commit d157387722
4 changed files with 38 additions and 33 deletions
+1
View File
@@ -24,6 +24,7 @@ tracing = { workspace = true }
thiserror = { workspace = true }
tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.9", optional = true }
semver = { version = "1", optional = true }
tokio = { version = "1", features = ["net"] }
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
version = "0.60"
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT
use std::{
io::{BufWriter, Error, ErrorKind, Read, Write},
os::unix::net::{UnixListener, UnixStream},
io::{BufWriter, Error, ErrorKind, Write},
os::unix::net::UnixStream,
path::PathBuf,
};
@@ -15,6 +15,7 @@ use tauri::{
plugin::{self, TauriPlugin},
AppHandle, Config, Manager, RunEvent, Runtime,
};
use tokio::io::AsyncReadExt;
pub fn init<R: Runtime>(cb: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
plugin::Builder::new("single-instance")
@@ -31,7 +32,7 @@ pub fn init<R: Runtime>(cb: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
// This process claims itself as singleton as likely none exists
socket_cleanup(&socket);
listen_for_other_instances(&socket, app.clone(), cb);
listen_for_other_instances(socket, app.clone(), cb);
}
_ => {
tracing::debug!(
@@ -92,42 +93,40 @@ fn notify_singleton(socket: &PathBuf) -> Result<(), Error> {
}
fn listen_for_other_instances<A: Runtime>(
socket: &PathBuf,
socket: PathBuf,
app: AppHandle<A>,
mut cb: Box<SingleInstanceCallback<A>>,
) {
match UnixListener::bind(socket) {
Ok(listener) => {
tauri::async_runtime::spawn(async move {
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let mut s = String::new();
match stream.read_to_string(&mut s) {
Ok(_) => {
let (cwd, args) = s.split_once("\0\0").unwrap_or_default();
let args: Vec<String> =
args.split('\0').map(String::from).collect();
cb(app.app_handle(), args, cwd.to_string());
}
Err(e) => {
tracing::debug!("single_instance failed to be notified: {e}")
}
tauri::async_runtime::spawn(async move {
match tokio::net::UnixListener::bind(socket) {
Ok(listener) => loop {
match listener.accept().await {
Ok((mut stream, _addr)) => {
let mut s = String::new();
match stream.read_to_string(&mut s).await {
Ok(_) => {
let (cwd, args) = s.split_once("\0\0").unwrap_or_default();
let args: Vec<String> =
args.split('\0').map(String::from).collect();
cb(app.app_handle(), args, cwd.to_string());
}
Err(e) => {
tracing::debug!("single_instance failed to be notified: {e}")
}
}
Err(err) => {
tracing::debug!("single_instance failed to be notified: {}", err);
continue;
}
}
Err(err) => {
tracing::debug!("single_instance failed to be notified: {}", err);
continue;
}
}
});
},
Err(err) => {
tracing::error!(
"single_instance failed to listen to other processes - launching normally: {}",
err
);
}
}
Err(err) => {
tracing::error!(
"single_instance failed to listen to other processes - launching normally: {}",
err
);
}
}
});
}