Bind prepared artifact selection against reclaim

This commit is contained in:
ms2k 2026-07-22 01:51:46 +09:00
commit 1bd1d395d9
5 changed files with 209 additions and 21 deletions

View file

@ -11,6 +11,7 @@ use lazaret_core::records::{
Generation, GenerationIdentity, Participant, ParticipantOwnership, ParticipantRole,
ParticipantState, PreparedArtifact,
};
use lazaret_linux::filesystem::SharedFileLock;
use lazaret_linux::process::{ProcessOwnershipContext, current_process_identity};
use lazaret_runtime_supervisor::sandbox::SandboxBundle;
use lazaret_runtime_supervisor::supervision::RuntimeSupervisorLaunch;
@ -65,6 +66,7 @@ pub struct BootstrapPlan<'a> {
pub configuration: &'a ResolvedDomainConfiguration,
pub generation_identity: GenerationIdentity,
pub prepared_artifact: &'a PreparedArtifact,
pub prepared_artifact_selection: Option<SharedFileLock>,
pub state_anchor_layout: &'a StateAnchorLayout<'a>,
pub runtime_launch: RuntimeSupervisorLaunch,
pub sandbox_bundle: &'a SandboxBundle,
@ -281,15 +283,21 @@ impl<'a> CoordinatorLifecycleAdapter<'a> {
Ok(())
}
fn lease_prepared_lower(&self) -> Result<(), LifecycleServiceError> {
fn lease_prepared_lower(&mut self) -> Result<(), LifecycleServiceError> {
self.state_anchor()
.begin_acquisition(
self.plan.generation_identity.generation_id,
self.plan.prepared_artifact,
self.plan.state_anchor_layout,
)
.map(|_| ())
.map_err(failure)
.map_err(failure)?;
drop(
self.plan
.prepared_artifact_selection
.take()
.ok_or_else(|| LifecycleServiceError("prepared artifact claim is absent".into()))?,
);
Ok(())
}
fn mount_state_anchor(&mut self) -> Result<(), LifecycleServiceError> {

View file

@ -132,7 +132,7 @@ fn run_private_client_bootstrap(arguments: &[OsString]) -> Result<(), PrivateCli
.enable_all()
.build()
.map_err(failure)?;
let selection = runtime
let mut selection = runtime
.block_on(select_runtime(
&configuration,
Arc::clone(&store),

View file

@ -16,6 +16,7 @@ use lazaret_core::records::{
Generation, GenerationIdentity, HostProcessOwnershipSlot, PreparedArtifact, Session,
};
use lazaret_ignored_workspace::git_common_directory;
use lazaret_linux::filesystem::SharedFileLock;
use lazaret_linux::filesystem::{RegularFileMailbox, mount_identity, namespace_identity};
use lazaret_linux::process::{
ProcessOwnershipContext, TargetCommand, child_broker, current_process_identity,
@ -76,6 +77,7 @@ pub struct ColdStartInputs<'a> {
pub generation: Generation,
pub prepared_artifact: &'a PreparedArtifact,
pub prepared_artifact_authority: &'a File,
pub prepared_artifact_selection: SharedFileLock,
pub prepared_artifact_label: &'a Path,
pub root_process: RootProcess,
pub container_id: ContainerId,
@ -135,6 +137,7 @@ pub struct ColdStart<'a> {
generation_identity: GenerationIdentity,
prepared_artifact: &'a PreparedArtifact,
state_anchor_layout: StateAnchorLayout<'a>,
prepared_artifact_selection: Option<SharedFileLock>,
runtime_launch: Option<RuntimeSupervisorLaunch>,
sandbox_bundle: SandboxBundle,
network_policy: lazaret_core::network::CanonicalDerivedNetworkPolicy,
@ -256,6 +259,7 @@ impl<'a> ColdStart<'a> {
generation_identity,
prepared_artifact: inputs.prepared_artifact,
state_anchor_layout,
prepared_artifact_selection: Some(inputs.prepared_artifact_selection),
runtime_launch: Some(runtime_launch),
sandbox_bundle,
network_policy,
@ -371,6 +375,7 @@ impl<'a> ColdStart<'a> {
configuration: self.configuration,
generation_identity: self.generation_identity.clone(),
prepared_artifact: self.prepared_artifact,
prepared_artifact_selection: self.prepared_artifact_selection.take(),
state_anchor_layout: &self.state_anchor_layout,
runtime_launch: self
.runtime_launch

View file

@ -11,6 +11,7 @@ use lazaret_core::identity::{
PrepareOperationId, SandboxId,
};
use lazaret_core::records::{DurableDecision, Generation, PreparedArtifact, VolumeKind};
use lazaret_linux::filesystem::SharedFileLock;
use lazaret_linux::platform::{PlatformObservation, check_platform};
use lazaret_linux::user_namespace::UserNamespaceMap;
use lazaret_prepared_lower::{
@ -32,6 +33,8 @@ use crate::prepared_worker::prepare_in_user_namespace;
use crate::recovery::recover_before_cold_start;
use crate::sandbox_init::PRIVATE_SANDBOX_INIT_ROLE;
const PREPARED_SELECTION_ATTEMPTS: usize = 16;
#[derive(Debug)]
pub enum RuntimeSelectionError {
PreparedLower(PreparedLowerError),
@ -78,6 +81,7 @@ pub struct RuntimeSelection {
catalog: Catalog,
artifact: PreparedArtifact,
artifact_authority: File,
artifact_selection: Option<SharedFileLock>,
artifact_label: PathBuf,
generation: Generation,
container_id: ContainerId,
@ -92,7 +96,7 @@ impl RuntimeSelection {
/// # Errors
/// Refuses a configuration other than the exact one used for selection.
pub fn cold_start_inputs<'a>(
&'a self,
&'a mut self,
configuration: &'a ResolvedDomainConfiguration,
) -> Result<ColdStartInputs<'a>, RuntimeSelectionError> {
if configuration.domain_id() != self.domain_id
@ -107,6 +111,10 @@ impl RuntimeSelection {
generation: self.generation.clone(),
prepared_artifact: &self.artifact,
prepared_artifact_authority: &self.artifact_authority,
prepared_artifact_selection: self
.artifact_selection
.take()
.ok_or_else(|| failure("selected artifact claim was already consumed"))?,
prepared_artifact_label: &self.artifact_label,
root_process: RootProcess::new(
SANDBOX_INIT_DESTINATION,
@ -174,17 +182,27 @@ async fn select_runtime_for_platform(
)
.await
.map_err(RuntimeSelectionError::PreparedLower)?;
let artifact = prepare_in_user_namespace(
configuration,
&image,
PrepareOperationId::new(random_id()?),
prepared_destinations(configuration),
&user_namespace_map,
)
.map_err(|error| failure(format!("mapped preparation worker failed: {error}")))?;
let artifact_authority = prepared
.open_artifact_authority(&artifact)
.map_err(RuntimeSelectionError::PreparedLower)?;
let mut selection = None;
for _ in 0..PREPARED_SELECTION_ATTEMPTS {
let artifact = prepare_in_user_namespace(
configuration,
&image,
PrepareOperationId::new(random_id()?),
prepared_destinations(configuration),
&user_namespace_map,
)
.map_err(|error| failure(format!("mapped preparation worker failed: {error}")))?;
if let Some(selected) = prepared
.select_artifact(&artifact)
.map_err(RuntimeSelectionError::PreparedLower)?
{
selection = Some(selected);
break;
}
}
let selection = selection
.ok_or_else(|| failure("prepared artifact was repeatedly reclaimed before selection"))?;
let (artifact, artifact_authority, artifact_selection) = selection.into_parts();
let artifact_label = PathBuf::from(std::ffi::OsString::from_vec(
configuration
.storage_root()
@ -218,6 +236,7 @@ async fn select_runtime_for_platform(
catalog,
artifact,
artifact_authority,
artifact_selection: Some(artifact_selection),
artifact_label,
generation,
container_id: ContainerId::new(random_id()?),

View file

@ -13,7 +13,7 @@ use lazaret_core::identity::{
use lazaret_core::records::{ArtifactState, Domain, DurableDecision, Generation, PreparedArtifact};
use lazaret_host_coordinator::cold_start::{ColdStart, ColdStartInputs};
use lazaret_host_coordinator::domain_store::HostCoordinatorDomainStore;
use lazaret_linux::filesystem::try_exclusive_flock;
use lazaret_linux::filesystem::{try_exclusive_flock, try_shared_flock};
use lazaret_linux::user_namespace::{
REQUIRED_SUBORDINATE_IDS, UserNamespaceIdRange, UserNamespaceMap,
};
@ -267,6 +267,10 @@ impl ColdStartFixture {
generation: self.generation.clone(),
prepared_artifact: &self.artifact,
prepared_artifact_authority: &self.artifact_authority,
prepared_artifact_selection: try_shared_flock(
File::open(&self.prepared_lower).unwrap(),
)
.unwrap(),
prepared_artifact_label: Path::new(
std::str::from_utf8(self.artifact.path.as_bytes()).unwrap(),
),