fix(auth): close Task 15 review round two
This commit is contained in:
@@ -38,7 +38,7 @@ type restoreDependencies struct {
|
||||
// public Create, which would re-acquire the non-reentrant lock and deadlock the transaction.
|
||||
checkpoint func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error)
|
||||
prepareRecovery func(context.Context, config.Installation, string) (PreflightResult, error)
|
||||
recover func(context.Context, config.Installation, PreflightResult, bool) error
|
||||
recover func(context.Context, config.Installation, PreflightResult, *stagedArchive, bool) error
|
||||
cleanupCheckpoint func(string) error
|
||||
acquireTransaction func(config.Installation) (*lifecycle.Transaction, error)
|
||||
runner archiveRunner
|
||||
@@ -119,11 +119,42 @@ func restoreWithDependencies(ctx context.Context, installation config.Installati
|
||||
}
|
||||
defer recovery.CloseArchive()
|
||||
|
||||
// Both immutable archives are staged while the lifecycle transaction is held and before
|
||||
// maintenance, service stops, or destination writes. Keeping both files open reserves their
|
||||
// combined staging capacity, so checkpoint recovery never needs a new allocation after a
|
||||
// candidate mutation has begun.
|
||||
candidateStage, err := preflight.StageArchive(ctx)
|
||||
if err != nil {
|
||||
cleanupErr := deps.cleanupCheckpoint(checkpoint.Path)
|
||||
return result, errors.Join(err, cleanupErr)
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := candidateStage.Close(); closeErr != nil {
|
||||
result = RestoreResult{}
|
||||
resultErr = errors.Join(resultErr, closeErr)
|
||||
}
|
||||
}()
|
||||
recoveryStage, err := recovery.StageArchive(ctx)
|
||||
if err != nil {
|
||||
cleanupErr := deps.cleanupCheckpoint(checkpoint.Path)
|
||||
return result, errors.Join(err, cleanupErr)
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := recoveryStage.Close(); closeErr != nil {
|
||||
result = RestoreResult{}
|
||||
resultErr = errors.Join(resultErr, closeErr)
|
||||
}
|
||||
}()
|
||||
if err := ensureCombinedRestoreCapacity(preflight, recovery); err != nil {
|
||||
cleanupErr := deps.cleanupCheckpoint(checkpoint.Path)
|
||||
return result, errors.Join(err, cleanupErr)
|
||||
}
|
||||
|
||||
state := restoreTransactionState{}
|
||||
defer func() {
|
||||
if state.recoveryRequired(resultErr) {
|
||||
recoveryContext, cancel := boundedCleanupContext()
|
||||
recoveryErr := deps.recover(recoveryContext, installation, recovery, state.wasRunning)
|
||||
recoveryErr := deps.recover(recoveryContext, installation, recovery, recoveryStage, state.wasRunning)
|
||||
cancel()
|
||||
if recoveryErr != nil {
|
||||
resultErr = errors.Join(resultErr, fmt.Errorf("restore recovery checkpoint: %w", recoveryErr))
|
||||
@@ -212,18 +243,8 @@ func restoreWithDependencies(ctx context.Context, installation config.Installati
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
staged, err := preflight.StageArchive(ctx)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := staged.Close(); closeErr != nil {
|
||||
result = RestoreResult{}
|
||||
resultErr = errors.Join(resultErr, closeErr)
|
||||
}
|
||||
}()
|
||||
state.mutated = true
|
||||
if err := restoreVerifiedEntries(ctx, installation, preflight, staged.file, deps.restoreFile, deps.restoreVolume); err != nil {
|
||||
if err := restoreVerifiedEntries(ctx, installation, preflight, candidateStage.file, deps.restoreFile, deps.restoreVolume); err != nil {
|
||||
return result, err
|
||||
}
|
||||
if err := deps.resetAuthenticationState(ctx, installation, deps.runner); err != nil {
|
||||
@@ -244,6 +265,24 @@ func restoreWithDependencies(ctx context.Context, installation config.Installati
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ensureCombinedRestoreCapacity(candidate, recovery PreflightResult) error {
|
||||
if candidate.freeBytes == nil || candidate.stagingRoot == "" || candidate.stagingRoot != recovery.stagingRoot {
|
||||
return errors.New("candidate and recovery archives do not share controlled restore staging")
|
||||
}
|
||||
if recovery.RequiredBytes > ^uint64(0)-candidate.RequiredBytes {
|
||||
return errors.New("combined candidate and recovery restore capacity exceeds supported size")
|
||||
}
|
||||
required := candidate.RequiredBytes + recovery.RequiredBytes
|
||||
freeBytes, err := candidate.freeBytes(candidate.stagingRoot)
|
||||
if err != nil {
|
||||
return errors.New("check combined candidate and recovery staging capacity")
|
||||
}
|
||||
if freeBytes < required {
|
||||
return fmt.Errorf("insufficient free disk space for combined candidate and recovery staging: need %d bytes, have %d", required, freeBytes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyRestoreTransaction(ctx context.Context, installation config.Installation, deps restoreDependencies) error {
|
||||
for _, name := range []string{"health", "doctor", "pi", "workspace"} {
|
||||
check := deps.verify[name]
|
||||
|
||||
Reference in New Issue
Block a user