- expose command-monitor conversations and preserve visible agent transcripts - add bounded polling and a dedicated shell interrupt tool - improve direct-provider images, skills, tool history, and usage handling - package and brand Galaxy Control across releases, installers, persistence, and docs
150 lines
5.8 KiB
Rust
150 lines
5.8 KiB
Rust
use std::sync::LazyLock;
|
|
|
|
use galaxy_core::channel::ChannelState;
|
|
use galaxyui::{Entity, ModelContext, SingletonEntity};
|
|
use ipc::ServerBuilder;
|
|
use parking_lot::Mutex;
|
|
use windows::core::Error;
|
|
use windows::Win32::Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, HANDLE};
|
|
use windows::Win32::System::Threading::CreateMutexW;
|
|
|
|
use super::service_impl::UriServiceImpl;
|
|
|
|
/// RAII wrapper around a Windows mutex HANDLE that closes it on drop.
|
|
struct MutexHandle(HANDLE);
|
|
|
|
// SAFETY: Windows kernel mutexes are valid to use from any thread. For example it says here:
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw#remarks
|
|
// > "Any thread of the calling process can specify the mutex-object handle in a call to one of the
|
|
// wait functions"
|
|
// The [`HANDLE`] is not Send or Sync b/c it's a common type used to point to a variety of Windows
|
|
// kernel objects, many of which are not safe to access from other threads.
|
|
unsafe impl Send for MutexHandle {}
|
|
unsafe impl Sync for MutexHandle {}
|
|
|
|
impl Drop for MutexHandle {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
let _ = CloseHandle(self.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The single-instance mutex handle. Lives for the process lifetime.
|
|
///
|
|
/// It's a complex type. Breaking it down:
|
|
/// * LazyLock - This type lets us go from un-initialized to initialized without `mut` and _not_
|
|
/// vice-versa.
|
|
/// * Mutex - Gives us interior mutability. Unlike `RefCell` it can be used in statics since it is
|
|
/// Sync. We don't actually need to access it on other threads though.
|
|
/// * Result - CreateMutexW might fail for reasons other than another process holding the lock. In
|
|
/// those cases, we store the error type.
|
|
/// * Option - `Some` if we are the sole instance, `None` if another instance holds the lock.
|
|
static SOLE_INSTANCE_MUTEX: LazyLock<Mutex<Result<Option<MutexHandle>, Error>>> =
|
|
LazyLock::new(|| Mutex::new(try_create_mutex()));
|
|
|
|
pub(super) fn uri_named_pipe_name() -> String {
|
|
format!("Galaxy{:?}_URI_CHANNEL", ChannelState::channel())
|
|
}
|
|
|
|
fn try_create_mutex() -> Result<Option<MutexHandle>, Error> {
|
|
// Scope this lock to the specific user session.
|
|
// https://learn.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces
|
|
// > "client processes can use the "Local\" prefix to explicitly create an object in their
|
|
// session namespace"
|
|
//
|
|
// NOTE: This lock name must stay in sync with `AppMutexName` in
|
|
// `script/windows/windows-installer.iss`, which the installer uses to detect whether Galaxy is
|
|
// running.
|
|
let name = format!("Local\\Galaxy{:?}_SingleInstance", ChannelState::channel())
|
|
.encode_utf16()
|
|
.chain(std::iter::once(0))
|
|
.collect::<Vec<u16>>();
|
|
let handle = unsafe { CreateMutexW(None, true, windows::core::PCWSTR(name.as_ptr())) };
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw#return-value
|
|
let already_exists = unsafe { GetLastError() } == ERROR_ALREADY_EXISTS;
|
|
handle
|
|
.inspect_err(|err| {
|
|
log::error!("Failed to create single-instance mutex: {err:#}");
|
|
})
|
|
.map(|handle| {
|
|
if already_exists {
|
|
// Another instance already owns this mutex. Close our duplicate handle.
|
|
unsafe {
|
|
let _ = CloseHandle(handle);
|
|
}
|
|
None
|
|
} else {
|
|
Some(MutexHandle(handle))
|
|
}
|
|
})
|
|
}
|
|
|
|
/// A singleton model that is responsible for ensuring there is only one instance of Galaxy running.
|
|
/// Uses a Windows named mutex (via `CreateMutexW`) which is a kernel object automatically cleaned
|
|
/// up by the OS when all handles are closed, including on crash.
|
|
pub(super) struct SingleInstanceManager {
|
|
_server: Option<ipc::Server>,
|
|
}
|
|
|
|
impl SingleInstanceManager {
|
|
/// Attempts to upgrade the current Galaxy instance to the "main" instance (i.e. the one that
|
|
/// holds the named mutex). This function enforces that a URI server is created iff the mutex
|
|
/// is held.
|
|
pub(super) fn new(ctx: &mut ModelContext<Self>) -> Self {
|
|
if let Ok(None) | Err(_) = &*SOLE_INSTANCE_MUTEX.lock() {
|
|
return Self { _server: None };
|
|
}
|
|
|
|
let (tx, rx) = async_channel::unbounded();
|
|
let server = match ServerBuilder::default()
|
|
.with_fixed_address(uri_named_pipe_name())
|
|
.with_service(UriServiceImpl::new(tx))
|
|
.build_and_run(ctx.background_executor())
|
|
{
|
|
Ok((server, _)) => {
|
|
ctx.spawn_stream_local(
|
|
rx,
|
|
|_single_instance_manager, event, ctx| {
|
|
for uri in event {
|
|
crate::uri::handle_incoming_uri(&uri, ctx);
|
|
}
|
|
},
|
|
|_, _| {},
|
|
);
|
|
server
|
|
}
|
|
Err(err) => {
|
|
log::error!("Failed to initialize UriService Server: {err:#}");
|
|
// If we failed to create a server, we can't receive URI requests so we drop the
|
|
// lock.
|
|
*SOLE_INSTANCE_MUTEX.lock() = Ok(None);
|
|
return Self { _server: None };
|
|
}
|
|
};
|
|
|
|
Self {
|
|
_server: Some(server),
|
|
}
|
|
}
|
|
|
|
/// Returns whether or not this process should be treated as the main instance of Galaxy.
|
|
///
|
|
/// NOTE: If an unexpected error occurs, we return `true` since it's better to open a second
|
|
/// instance than to fail to create a first instance.
|
|
pub(super) fn is_sole_running_instance() -> Result<bool, Error> {
|
|
SOLE_INSTANCE_MUTEX
|
|
.lock()
|
|
.as_ref()
|
|
.map(|handle| handle.is_some())
|
|
.map_err(Clone::clone)
|
|
}
|
|
}
|
|
|
|
impl Entity for SingleInstanceManager {
|
|
type Event = ();
|
|
}
|
|
|
|
impl SingletonEntity for SingleInstanceManager {}
|