-
-
Notifications
You must be signed in to change notification settings - Fork 214
feat: single-instance application support #407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,7 +20,7 @@ mod connect; | |||||||||||
| mod connect_async; | ||||||||||||
| mod listen; | ||||||||||||
|
|
||||||||||||
| pub use connect::{FrontendEventReader, FrontendRequestWriter, connect}; | ||||||||||||
| pub use connect::{FrontendEventReader, FrontendRequestWriter, connect, try_connect}; | ||||||||||||
| pub use connect_async::{AsyncFrontendEventReader, AsyncFrontendRequestWriter, connect_async}; | ||||||||||||
| pub use listen::AsyncFrontendListener; | ||||||||||||
|
|
||||||||||||
|
|
@@ -299,3 +299,18 @@ pub fn default_socket_path() -> Result<PathBuf, SocketPathError> { | |||||||||||
| .join("Caches") | ||||||||||||
| .join(LAN_MOUSE_SOCKET_NAME)) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// Check if a lan-mouse service is already running by probing the IPC socket. | ||||||||||||
| #[cfg(unix)] | ||||||||||||
| pub fn is_service_running() -> bool { | ||||||||||||
| let Ok(socket_path) = default_socket_path() else { | ||||||||||||
| return false; | ||||||||||||
| }; | ||||||||||||
| std::os::unix::net::UnixStream::connect(socket_path).is_ok() | ||||||||||||
|
||||||||||||
| std::os::unix::net::UnixStream::connect(socket_path).is_ok() | |
| // Avoid establishing a real IPC connection, which would be interpreted as a | |
| // frontend connection by the daemon and trigger a sync broadcast. Checking | |
| // for the existence of the socket path is a side-effect-free approximation. | |
| std::fs::metadata(socket_path).is_ok() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The observation about the spurious Sync is valid, but the suggested fix (std::fs::metadata) would break single-instance detection — a stale socket file left behind by a crashed daemon would give a false positive, and we'd skip spawning when no daemon is actually running. The existing listener code in listen.rs already handles this case correctly by attempting a real connection to distinguish stale sockets from live daemons.
The extra Sync is harmless — it's one redundant state broadcast at startup. Eliminating the double-connect would require passing the connection across the crate boundary (main.rs → GTK), which is a larger refactor for marginal gain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is exactly, why I am explicitly making a connection! A sync request also shouldn't cause any issues.
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -73,18 +73,26 @@ fn run() -> Result<(), LanMouseError> { | |||||||||||
| // run a frontend | ||||||||||||
| #[cfg(feature = "gtk")] | ||||||||||||
| { | ||||||||||||
| let mut service = start_service()?; | ||||||||||||
| // Only spawn a new daemon if one isn't already running | ||||||||||||
| let mut service = if lan_mouse_ipc::is_service_running() { | ||||||||||||
| log::info!("daemon already running, connecting to existing instance"); | ||||||||||||
| None | ||||||||||||
| } else { | ||||||||||||
| Some(start_service()?) | ||||||||||||
| }; | ||||||||||||
| let res = lan_mouse_gtk::run(); | ||||||||||||
|
Comment on lines
+76
to
83
|
||||||||||||
| #[cfg(unix)] | ||||||||||||
| { | ||||||||||||
| // on unix we give the service a chance to terminate gracefully | ||||||||||||
| let pid = service.id() as libc::pid_t; | ||||||||||||
| unsafe { | ||||||||||||
| libc::kill(pid, libc::SIGINT); | ||||||||||||
| if let Some(ref mut service) = service { | ||||||||||||
| #[cfg(unix)] | ||||||||||||
| { | ||||||||||||
| // on unix we give the service a chance to terminate gracefully | ||||||||||||
| let pid = service.id() as libc::pid_t; | ||||||||||||
| unsafe { | ||||||||||||
| libc::kill(pid, libc::SIGINT); | ||||||||||||
| } | ||||||||||||
| service.wait()?; | ||||||||||||
| } | ||||||||||||
| service.wait()?; | ||||||||||||
| service.kill()?; | ||||||||||||
|
||||||||||||
| service.kill()?; | |
| #[cfg(not(unix))] | |
| { | |
| service.kill()?; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pre-existing behavior — the original code had the same wait() then kill() sequence. This PR just moved it inside the if let Some guard. Fixing the redundant kill is a good cleanup but out of scope for this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
try_connect()fails, the GTK frontend spawns a daemon process but discards theChildhandle, so it cannot be terminated on GUI exit. In the race wheremain.rsdecides not to spawn (because the daemon was running) but the daemon dies before GTK connects, this path will leave a newly spawned daemon running after the GUI closes. Consider centralizing daemon spawning/lifecycle management inmain.rs(or returning the child handle/ownership information) so that only daemons spawned by the GUI are cleaned up.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is by design. The fallback path only triggers in a rare race condition (daemon dies between main.rs probe and GTK connect). The spawned daemon runs independently, same as if the user had run
lan-mouse daemonseparately — it manages its own lifecycle and cleans up its socket on drop. Centralizing the lifecycle would require threading aChildhandle back through the GTK crate boundary, adding complexity for an edge case where the intended behavior (daemon keeps running) is already correct.