Skip to content

Commit 70e98b1

Browse files
committed
phd: getting guest OS kinds should not involve locks
Artifact types are known ahead of time when the PHD artifact manifest is parsed. These won't change throughout a test, really only the path that those artifacts are locally cached at is the only element of an artifact that *may* change - and even then, only if the artifact is not already local in manifest or cache. So, move this mutable component of StoredArtifact behind a mutex, and let the rest of the artifact's description be accessible any time. The corresponding shuffling means that accessing a guest OS kind no longer implies downloading the referenced aritfact, an edge case which no one has noticed and no one will miss probably!
1 parent 368a222 commit 70e98b1

5 files changed

Lines changed: 51 additions & 47 deletions

File tree

phd-tests/framework/src/artifacts/store.rs

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,30 @@ use std::collections::BTreeMap;
2020
use std::fs::File;
2121
use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};
2222
use std::time::Duration;
23-
use tokio::sync::Mutex;
23+
use tokio::sync::{Mutex, MutexGuard};
2424
use tracing::{debug, info, warn};
2525

2626
#[derive(Debug)]
2727
struct StoredArtifact {
2828
description: super::Artifact,
29-
cached_path: Option<Utf8PathBuf>,
29+
cached_path: Mutex<Option<Utf8PathBuf>>,
3030
}
3131

3232
impl StoredArtifact {
3333
fn new(description: super::Artifact) -> Self {
34-
Self { description, cached_path: None }
34+
Self { description, cached_path: Mutex::new(None) }
3535
}
3636

3737
async fn ensure(
38-
&mut self,
38+
&self,
3939
local_dir: &Utf8Path,
4040
downloader: &DownloadConfig,
4141
) -> anyhow::Result<Utf8PathBuf> {
42+
let mut path_guard = self.cached_path.lock().await;
43+
4244
// If the artifact already exists and has been verified, return the path
4345
// to it straightaway.
44-
if let Some(path) = &self.cached_path {
46+
if let Some(path) = path_guard.as_ref() {
4547
debug!(%path, "Verified artifact already exists");
4648
return Ok(path.clone());
4749
}
@@ -67,7 +69,7 @@ impl StoredArtifact {
6769
// The file is in the right place and has the right hash (if that
6870
// was checked), so mark it as cached and return the cached path.
6971
debug!(%path, "Locally-sourced artifact is valid, caching its path");
70-
self.cached_path = Some(path.clone());
72+
*path_guard = Some(path.clone());
7173
return Ok(path.clone());
7274
}
7375

@@ -91,7 +93,7 @@ impl StoredArtifact {
9193
if file_hash_equals(&maybe_path, expected_digest).is_ok() {
9294
debug!(%maybe_path,
9395
"Valid artifact already exists, caching its path");
94-
return self.cache_path(maybe_path);
96+
return self.cache_path(path_guard, maybe_path);
9597
} else {
9698
warn!(%maybe_path, "Existing artifact is invalid, deleting it");
9799
std::fs::remove_file(&maybe_path)?;
@@ -140,11 +142,12 @@ impl StoredArtifact {
140142
permissions.set_readonly(true);
141143
new_file.set_permissions(permissions)?;
142144

143-
self.cache_path(maybe_path)
145+
self.cache_path(path_guard, maybe_path)
144146
}
145147

146148
fn cache_path(
147-
&mut self,
149+
&self,
150+
mut path_guard: MutexGuard<'_, Option<Utf8PathBuf>>,
148151
mut path: Utf8PathBuf,
149152
) -> anyhow::Result<Utf8PathBuf> {
150153
if let Some(ref untar_path) = self.description.untar {
@@ -166,15 +169,15 @@ impl StoredArtifact {
166169
}
167170
};
168171

169-
self.cached_path = Some(path.clone());
172+
*path_guard = Some(path.clone());
170173
Ok(path)
171174
}
172175
}
173176

174177
#[derive(Debug)]
175178
pub struct Store {
176179
local_dir: Utf8PathBuf,
177-
artifacts: BTreeMap<String, Mutex<StoredArtifact>>,
180+
artifacts: BTreeMap<String, StoredArtifact>,
178181
downloader: DownloadConfig,
179182
}
180183

@@ -199,7 +202,7 @@ impl Store {
199202
let Manifest { artifacts, remote_server_uris } = manifest;
200203
let artifacts = artifacts
201204
.into_iter()
202-
.map(|(k, v)| (k, Mutex::new(StoredArtifact::new(v))))
205+
.map(|(k, v)| (k, StoredArtifact::new(v)))
203206
.collect();
204207

205208
let buildomat_backoff = backoff::ExponentialBackoffBuilder::new()
@@ -281,7 +284,7 @@ impl Store {
281284

282285
let _old = self.artifacts.insert(
283286
BASE_PROPOLIS_ARTIFACT.to_string(),
284-
Mutex::new(StoredArtifact::new(artifact)),
287+
StoredArtifact::new(artifact),
285288
);
286289
assert!(_old.is_none());
287290
Ok(())
@@ -335,24 +338,36 @@ impl Store {
335338

336339
let _old = self.artifacts.insert(
337340
CRUCIBLE_DOWNSTAIRS_ARTIFACT.to_string(),
338-
Mutex::new(StoredArtifact::new(artifact)),
341+
StoredArtifact::new(artifact),
339342
);
340343
assert!(_old.is_none());
341344
Ok(())
342345
}
343346
}
344347
}
345348

349+
pub fn get_guest_os_image_kind(
350+
&self,
351+
artifact_name: &str,
352+
) -> anyhow::Result<GuestOsKind> {
353+
let entry = self.get_artifact(artifact_name)?;
354+
match entry.description.kind {
355+
super::ArtifactKind::GuestOs(kind) => Ok(kind),
356+
_ => Err(anyhow::anyhow!(
357+
"artifact {artifact_name} is not a guest OS image"
358+
)),
359+
}
360+
}
361+
346362
pub async fn get_guest_os_image(
347363
&self,
348364
artifact_name: &str,
349365
) -> anyhow::Result<(Utf8PathBuf, GuestOsKind)> {
350366
let entry = self.get_artifact(artifact_name)?;
351-
let mut guard = entry.lock().await;
352-
match guard.description.kind {
367+
match entry.description.kind {
353368
super::ArtifactKind::GuestOs(kind) => {
354369
let path =
355-
guard.ensure(&self.local_dir, &self.downloader).await?;
370+
entry.ensure(&self.local_dir, &self.downloader).await?;
356371
Ok((path, kind))
357372
}
358373
_ => Err(anyhow::anyhow!(
@@ -366,10 +381,9 @@ impl Store {
366381
artifact_name: &str,
367382
) -> anyhow::Result<Utf8PathBuf> {
368383
let entry = self.get_artifact(artifact_name)?;
369-
let mut guard = entry.lock().await;
370-
match guard.description.kind {
384+
match entry.description.kind {
371385
super::ArtifactKind::Bootrom => {
372-
guard.ensure(&self.local_dir, &self.downloader).await
386+
entry.ensure(&self.local_dir, &self.downloader).await
373387
}
374388
_ => Err(anyhow::anyhow!(
375389
"artifact {artifact_name} is not a bootrom"
@@ -382,10 +396,9 @@ impl Store {
382396
artifact_name: &str,
383397
) -> anyhow::Result<Utf8PathBuf> {
384398
let entry = self.get_artifact(artifact_name)?;
385-
let mut guard = entry.lock().await;
386-
match guard.description.kind {
399+
match entry.description.kind {
387400
super::ArtifactKind::PropolisServer => {
388-
guard.ensure(&self.local_dir, &self.downloader).await
401+
entry.ensure(&self.local_dir, &self.downloader).await
389402
}
390403
_ => Err(anyhow::anyhow!(
391404
"artifact {artifact_name} is not a Propolis server"
@@ -395,21 +408,17 @@ impl Store {
395408

396409
pub async fn get_crucible_downstairs(&self) -> anyhow::Result<Utf8PathBuf> {
397410
let entry = self.get_artifact(CRUCIBLE_DOWNSTAIRS_ARTIFACT)?;
398-
let mut guard = entry.lock().await;
399-
match guard.description.kind {
411+
match entry.description.kind {
400412
super::ArtifactKind::CrucibleDownstairs => {
401-
guard.ensure(&self.local_dir, &self.downloader).await
413+
entry.ensure(&self.local_dir, &self.downloader).await
402414
}
403415
_ => Err(anyhow::anyhow!(
404416
"artifact {CRUCIBLE_DOWNSTAIRS_ARTIFACT} is not a Crucible downstairs binary",
405417
)),
406418
}
407419
}
408420

409-
fn get_artifact(
410-
&self,
411-
name: &str,
412-
) -> anyhow::Result<&Mutex<StoredArtifact>> {
421+
fn get_artifact(&self, name: &str) -> anyhow::Result<&StoredArtifact> {
413422
self.artifacts.get(name).ok_or_else(|| {
414423
anyhow::anyhow!("artifact {name} not found in store")
415424
})
@@ -449,10 +458,9 @@ impl Store {
449458
untar: None,
450459
};
451460

452-
let _old: Option<Mutex<StoredArtifact>> = self.artifacts.insert(
453-
artifact_name.to_string(),
454-
Mutex::new(StoredArtifact::new(artifact)),
455-
);
461+
let _old: Option<StoredArtifact> = self
462+
.artifacts
463+
.insert(artifact_name.to_string(), StoredArtifact::new(artifact));
456464
assert!(_old.is_none());
457465

458466
Ok(())

phd-tests/framework/src/lib.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,9 @@ impl TestCtx {
150150
self.framework.default_guest_os_artifact()
151151
}
152152

153-
/// Yields the guest OS adapter corresponding to the default guest OS
154-
/// artifact.
155-
pub async fn default_guest_os_kind(&self) -> anyhow::Result<GuestOsKind> {
156-
self.framework.default_guest_os_kind().await
153+
/// Returns the guest OS kind corresponding to the default guest OS artifact.
154+
pub fn default_guest_os_kind(&self) -> anyhow::Result<GuestOsKind> {
155+
self.framework.default_guest_os_kind()
157156
}
158157

159158
/// Indicates whether the disk factory in this framework supports the
@@ -366,12 +365,9 @@ impl Framework {
366365

367366
/// Yields the guest OS adapter corresponding to the default guest OS
368367
/// artifact.
369-
pub async fn default_guest_os_kind(&self) -> anyhow::Result<GuestOsKind> {
370-
Ok(self
371-
.artifact_store
372-
.get_guest_os_image(&self.default_guest_os_artifact)
373-
.await?
374-
.1)
368+
pub fn default_guest_os_kind(&self) -> anyhow::Result<GuestOsKind> {
369+
self.artifact_store
370+
.get_guest_os_image_kind(&self.default_guest_os_artifact)
375371
}
376372

377373
/// Indicates whether the disk factory in this framework supports the

phd-tests/tests/src/disk.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ async fn mount_in_memory_disk(
114114

115115
#[phd_testcase]
116116
async fn in_memory_backend_smoke_test(ctx: &TestCtx) {
117-
if ctx.default_guest_os_kind().await?.is_windows() {
117+
if ctx.default_guest_os_kind()?.is_windows() {
118118
phd_skip!(
119119
"in-memory disk tests use mount options not supported by Cygwin"
120120
);

phd-tests/tests/src/framework.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ async fn multiline_serial_test(ctx: &TestCtx) {
1919

2020
#[phd_testcase]
2121
async fn long_line_serial_test(ctx: &TestCtx) {
22-
let os = ctx.default_guest_os_kind().await?;
22+
let os = ctx.default_guest_os_kind()?;
2323
if matches!(
2424
os,
2525
GuestOsKind::WindowsServer2016 | GuestOsKind::WindowsServer2019

phd-tests/tests/src/hyperv.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ async fn hyperv_reference_tsc_elapsed_time_test(ctx: &TestCtx) {
284284
Ok(())
285285
}
286286

287-
if ctx.default_guest_os_kind().await?.is_windows() {
287+
if ctx.default_guest_os_kind()?.is_windows() {
288288
phd_skip!("test requires a guest with /proc/timer_list in procfs");
289289
}
290290

0 commit comments

Comments
 (0)