feat(core): Provision cluster creation in an attempt to not have race conditions
This commit is contained in:
parent
c338d09c60
commit
a9481a5789
5 changed files with 45 additions and 4 deletions
|
|
@ -48,6 +48,37 @@ impl ClusterManager {
|
|||
pub async fn create(
|
||||
state: &LauncherState,
|
||||
options: CreateClusterOptions,
|
||||
) -> LauncherResult<Cluster> {
|
||||
let _guard = state.provisioning.lock().await;
|
||||
Self::create_core(state, options).await
|
||||
}
|
||||
|
||||
/// Create a cluster for a version/loader that doesn't exist yet, and provision it.
|
||||
#[tracing::instrument(level = "debug", skip(state))]
|
||||
pub async fn create_provisioned(
|
||||
state: &LauncherState,
|
||||
options: CreateClusterOptions,
|
||||
) -> LauncherResult<Option<Cluster>> {
|
||||
let _guard = state.provisioning.lock().await;
|
||||
if cluster_dao::find_by_version_loader(
|
||||
&state.services.db,
|
||||
&options.mc_version,
|
||||
options.mc_loader as i64,
|
||||
)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
Self::create_core(state, options).await.map(Some)
|
||||
}
|
||||
|
||||
/// Folder-resolve + insert core shared by `create` and `create_provisioned`.
|
||||
/// Callers MUST hold `state.provisioning` — this does not lock.
|
||||
#[tracing::instrument(level = "debug", skip(state))]
|
||||
async fn create_core(
|
||||
state: &LauncherState,
|
||||
options: CreateClusterOptions,
|
||||
) -> LauncherResult<Cluster> {
|
||||
let name = Self::sanitize_name(&options.name);
|
||||
if name.is_empty() {
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ pub async fn ensure_from_bundles(state: &LauncherState) -> LauncherResult<Vec<Cl
|
|||
|
||||
let mc_version = group.mc_version.clone();
|
||||
let name = format!("{mc_version} {loader}");
|
||||
match ClusterManager::create(
|
||||
match ClusterManager::create_provisioned(
|
||||
state,
|
||||
CreateClusterOptions::new(name, mc_version.clone(), loader),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(cluster) => {
|
||||
Ok(Some(cluster)) => {
|
||||
tracing::info!(
|
||||
cluster_id = cluster.id,
|
||||
mc_version = %cluster.mc_version,
|
||||
|
|
@ -54,6 +54,7 @@ pub async fn ensure_from_bundles(state: &LauncherState) -> LauncherResult<Vec<Cl
|
|||
);
|
||||
created.push(cluster);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
mc_version = %mc_version,
|
||||
|
|
@ -100,13 +101,13 @@ pub async fn ensure_from_versions(state: &LauncherState) -> LauncherResult<Vec<C
|
|||
}
|
||||
|
||||
let name = format!("{mc_version} {loader}");
|
||||
match ClusterManager::create(
|
||||
match ClusterManager::create_provisioned(
|
||||
state,
|
||||
CreateClusterOptions::new(name, mc_version.clone(), loader),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(cluster) => {
|
||||
Ok(Some(cluster)) => {
|
||||
tracing::info!(
|
||||
cluster_id = cluster.id,
|
||||
mc_version = %cluster.mc_version,
|
||||
|
|
@ -115,6 +116,7 @@ pub async fn ensure_from_versions(state: &LauncherState) -> LauncherResult<Vec<C
|
|||
);
|
||||
created.push(cluster);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
mc_version = %mc_version,
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ pub async fn ephemeral_state() -> LauncherResult<Arc<LauncherState>> {
|
|||
images: crate::images::ImageCacheStore::new(),
|
||||
games: crate::game::GameProcessManager::new(),
|
||||
discord: crate::discord::DiscordRpc::spawn(false),
|
||||
provisioning: tokio::sync::Mutex::new(()),
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ impl RecoveryReport {
|
|||
pub async fn reconstruct_from_disk(state: &LauncherState) -> LauncherResult<RecoveryReport> {
|
||||
let mut report = RecoveryReport::default();
|
||||
|
||||
// Hold the provisioning lock across the whole scan+adopt so a concurrent
|
||||
// cluster create (folder-on-disk written before its DB row commits) can't be
|
||||
// misclassified as an orphan and adopted into a duplicate row.
|
||||
let _guard = state.provisioning.lock().await;
|
||||
|
||||
let clusters_dir = paths::clusters_dir()?;
|
||||
let orphans = orphan_cluster_folders(&state.services.db, &clusters_dir).await?;
|
||||
if orphans.is_empty() {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ pub struct LauncherState {
|
|||
pub images: ImageCacheStore,
|
||||
pub games: crate::game::GameProcessManager,
|
||||
pub discord: DiscordRpc,
|
||||
pub provisioning: Mutex<()>,
|
||||
}
|
||||
|
||||
impl LauncherState {
|
||||
|
|
@ -75,6 +76,7 @@ impl LauncherState {
|
|||
images: ImageCacheStore::new(),
|
||||
games: crate::game::GameProcessManager::new(),
|
||||
discord,
|
||||
provisioning: Mutex::new(()),
|
||||
});
|
||||
|
||||
STATE
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue