fix(core): disallow launching multiple instances so fast

This commit is contained in:
LynithDev 2026-08-01 13:12:52 +02:00
parent 44c153dd91
commit 038dc94725
3 changed files with 38 additions and 2 deletions

View file

@ -33,6 +33,11 @@ pub enum GameError {
#[error("another cluster ({0}) is already running in the same directory")]
DirectoryInUse(i64),
#[error(
"{0} is already running in the shared game directory; close it before launching another cluster"
)]
SharedDirectoryBusy(String),
#[error("failed to spawn the game process: {0}")]
Spawn(String),
}

View file

@ -60,12 +60,22 @@ pub async fn launch_cluster(
let existing = state.clusters.get(cluster_id).await?;
let game_dir = existing.game_dir()?;
let dedicated = existing.uses_dedicated_dir();
// Every cluster without a dedicated folder plays out of the same shared
// directory, so a second game there would materialize its content over the
// running one's. `dir_in_use_by` waves a cluster past its own session, which
// parallel launches make reachable, so the directory is checked outright.
if !dedicated && let Some(other) = state.games.dir_in_use(&game_dir) {
tracing::warn!(cluster_id, other, "shared game dir busy; refusing launch");
let name = running_cluster_name(state, other).await;
return Err(GameError::SharedDirectoryBusy(name).into());
}
if let Some(other) = state.games.dir_in_use_by(&game_dir, cluster_id) {
return Err(GameError::DirectoryInUse(other).into());
}
let dedicated = existing.uses_dedicated_dir();
let progress = GroupedProgressSession::start(
&state.services.events,
format!("Launching {}", existing.name),
@ -406,6 +416,16 @@ pub async fn launch_cluster(
Ok(LaunchedGame { cluster_id, pid })
}
/// Names the cluster in the user's way for a refusal they have to act on.
/// Falls back to the id, which is still better than no answer at all.
async fn running_cluster_name(state: &Arc<LauncherState>, cluster_id: i64) -> String {
state
.clusters
.get(cluster_id)
.await
.map_or_else(|_| format!("Cluster {cluster_id}"), |cluster| cluster.name)
}
/// Cut the game loose from the launcher's process group / console, so signals
/// aimed at the launcher (a terminal Ctrl-C, a console window closing) don't
/// reach the game as collateral.

View file

@ -118,6 +118,17 @@ impl GameProcessManager {
self.dirs.lock().unwrap().insert(cluster_id, dir);
}
/// Like [`Self::dir_in_use_by`], but counts a cluster's own session too. The
/// shared game directory holds one game at a time, whoever it belongs to.
pub fn dir_in_use(&self, dir: &Path) -> Option<i64> {
self.dirs
.lock()
.unwrap()
.iter()
.find(|(_, d)| d.as_path() == dir)
.map(|(id, _)| *id)
}
pub fn dir_in_use_by(&self, dir: &Path, exclude: i64) -> Option<i64> {
self.dirs
.lock()