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(
|
pub async fn create(
|
||||||
state: &LauncherState,
|
state: &LauncherState,
|
||||||
options: CreateClusterOptions,
|
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> {
|
) -> LauncherResult<Cluster> {
|
||||||
let name = Self::sanitize_name(&options.name);
|
let name = Self::sanitize_name(&options.name);
|
||||||
if name.is_empty() {
|
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 mc_version = group.mc_version.clone();
|
||||||
let name = format!("{mc_version} {loader}");
|
let name = format!("{mc_version} {loader}");
|
||||||
match ClusterManager::create(
|
match ClusterManager::create_provisioned(
|
||||||
state,
|
state,
|
||||||
CreateClusterOptions::new(name, mc_version.clone(), loader),
|
CreateClusterOptions::new(name, mc_version.clone(), loader),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(cluster) => {
|
Ok(Some(cluster)) => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
cluster_id = cluster.id,
|
cluster_id = cluster.id,
|
||||||
mc_version = %cluster.mc_version,
|
mc_version = %cluster.mc_version,
|
||||||
|
|
@ -54,6 +54,7 @@ pub async fn ensure_from_bundles(state: &LauncherState) -> LauncherResult<Vec<Cl
|
||||||
);
|
);
|
||||||
created.push(cluster);
|
created.push(cluster);
|
||||||
}
|
}
|
||||||
|
Ok(None) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
mc_version = %mc_version,
|
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}");
|
let name = format!("{mc_version} {loader}");
|
||||||
match ClusterManager::create(
|
match ClusterManager::create_provisioned(
|
||||||
state,
|
state,
|
||||||
CreateClusterOptions::new(name, mc_version.clone(), loader),
|
CreateClusterOptions::new(name, mc_version.clone(), loader),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(cluster) => {
|
Ok(Some(cluster)) => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
cluster_id = cluster.id,
|
cluster_id = cluster.id,
|
||||||
mc_version = %cluster.mc_version,
|
mc_version = %cluster.mc_version,
|
||||||
|
|
@ -115,6 +116,7 @@ pub async fn ensure_from_versions(state: &LauncherState) -> LauncherResult<Vec<C
|
||||||
);
|
);
|
||||||
created.push(cluster);
|
created.push(cluster);
|
||||||
}
|
}
|
||||||
|
Ok(None) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
mc_version = %mc_version,
|
mc_version = %mc_version,
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,7 @@ pub async fn ephemeral_state() -> LauncherResult<Arc<LauncherState>> {
|
||||||
images: crate::images::ImageCacheStore::new(),
|
images: crate::images::ImageCacheStore::new(),
|
||||||
games: crate::game::GameProcessManager::new(),
|
games: crate::game::GameProcessManager::new(),
|
||||||
discord: crate::discord::DiscordRpc::spawn(false),
|
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> {
|
pub async fn reconstruct_from_disk(state: &LauncherState) -> LauncherResult<RecoveryReport> {
|
||||||
let mut report = RecoveryReport::default();
|
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 clusters_dir = paths::clusters_dir()?;
|
||||||
let orphans = orphan_cluster_folders(&state.services.db, &clusters_dir).await?;
|
let orphans = orphan_cluster_folders(&state.services.db, &clusters_dir).await?;
|
||||||
if orphans.is_empty() {
|
if orphans.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ pub struct LauncherState {
|
||||||
pub images: ImageCacheStore,
|
pub images: ImageCacheStore,
|
||||||
pub games: crate::game::GameProcessManager,
|
pub games: crate::game::GameProcessManager,
|
||||||
pub discord: DiscordRpc,
|
pub discord: DiscordRpc,
|
||||||
|
pub provisioning: Mutex<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LauncherState {
|
impl LauncherState {
|
||||||
|
|
@ -75,6 +76,7 @@ impl LauncherState {
|
||||||
images: ImageCacheStore::new(),
|
images: ImageCacheStore::new(),
|
||||||
games: crate::game::GameProcessManager::new(),
|
games: crate::game::GameProcessManager::new(),
|
||||||
discord,
|
discord,
|
||||||
|
provisioning: Mutex::new(()),
|
||||||
});
|
});
|
||||||
|
|
||||||
STATE
|
STATE
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue