Skip to content

Commit 0b29cb1

Browse files
committed
fix: harden configure rollback semantics (PR #416)
1 parent ee78c4c commit 0b29cb1

File tree

1 file changed

+60
-21
lines changed

1 file changed

+60
-21
lines changed

crates/pet/src/jsonrpc.rs

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,6 @@ fn apply_configure_options(
665665
environment_directories: Option<Vec<PathBuf>>,
666666
) -> Result<(), String> {
667667
let mut state = configuration.write().unwrap();
668-
let previous_config = state.config.clone();
669668
let mut next_config = state.config.clone();
670669
let next_generation = state.generation + 1;
671670

@@ -687,18 +686,27 @@ fn apply_configure_options(
687686
next_config
688687
);
689688

690-
if let Err(panic_payload) = panic::catch_unwind(AssertUnwindSafe(|| {
691-
configure_locators(locators, &next_config);
692-
})) {
693-
let panic_message = panic_payload_message(panic_payload.as_ref());
694-
error!(
695-
"Locator configuration panicked for generation {}: {}",
696-
next_generation, panic_message
697-
);
698-
rollback_locator_config(locators, &previous_config, next_generation);
699-
return Err(format!(
700-
"Locator configuration failed for generation {next_generation}: {panic_message}"
701-
));
689+
let mut configured_locator_count = 0;
690+
for locator in locators.iter() {
691+
if let Err(panic_payload) = panic::catch_unwind(AssertUnwindSafe(|| {
692+
locator.configure(&next_config);
693+
})) {
694+
let panic_message = panic_payload_message(panic_payload.as_ref());
695+
error!(
696+
"Locator configuration panicked for generation {}: {}",
697+
next_generation, panic_message
698+
);
699+
rollback_locator_config(
700+
locators,
701+
&state.config,
702+
next_generation,
703+
configured_locator_count + 1,
704+
);
705+
return Err(format!(
706+
"Locator configuration failed for generation {next_generation}: {panic_message}"
707+
));
708+
}
709+
configured_locator_count += 1;
702710
}
703711

704712
if let Some(cache_directory) = cache_directory {
@@ -710,7 +718,12 @@ fn apply_configure_options(
710718
"Cache directory configuration panicked for generation {}: {}",
711719
next_generation, panic_message
712720
);
713-
rollback_locator_config(locators, &previous_config, next_generation);
721+
rollback_locator_config(
722+
locators,
723+
&state.config,
724+
next_generation,
725+
configured_locator_count,
726+
);
714727
return Err(format!(
715728
"Cache directory configuration failed for generation {next_generation}: {panic_message}"
716729
));
@@ -731,15 +744,19 @@ fn rollback_locator_config(
731744
locators: &Arc<Vec<Arc<dyn Locator>>>,
732745
previous_config: &Configuration,
733746
failed_generation: u64,
747+
configured_locator_count: usize,
734748
) {
735749
if let Err(panic_payload) = panic::catch_unwind(AssertUnwindSafe(|| {
736-
configure_locators(locators, previous_config);
750+
for locator in locators.iter().take(configured_locator_count) {
751+
locator.configure(previous_config);
752+
}
737753
})) {
738754
error!(
739-
"Rollback after failed locator configuration for generation {} also panicked: {}",
755+
"Rollback after failed locator configuration for generation {} also panicked: {}. Aborting process to avoid continuing with inconsistent locator state.",
740756
failed_generation,
741757
panic_payload_message(panic_payload.as_ref())
742758
);
759+
std::process::abort();
743760
}
744761
}
745762

@@ -1339,7 +1356,9 @@ mod tests {
13391356
configured_workspace_directories: Mutex<Option<Vec<PathBuf>>>,
13401357
}
13411358

1342-
struct PanicConfigureLocator;
1359+
struct PanicConfigureLocator {
1360+
configured_workspace_directories: Mutex<Option<Vec<PathBuf>>>,
1361+
}
13431362

13441363
struct RecordingConfigureLocator {
13451364
configured_workspace_directories: Mutex<Option<Vec<PathBuf>>>,
@@ -1404,8 +1423,12 @@ mod tests {
14041423
LocatorKind::Venv
14051424
}
14061425

1407-
fn configure(&self, _config: &Configuration) {
1408-
panic!("configure boom");
1426+
fn configure(&self, config: &Configuration) {
1427+
*self.configured_workspace_directories.lock().unwrap() =
1428+
config.workspace_directories.clone();
1429+
if config.workspace_directories.is_some() || config.conda_executable.is_some() {
1430+
panic!("configure boom");
1431+
}
14091432
}
14101433

14111434
fn supported_categories(&self) -> Vec<PythonEnvironmentKind> {
@@ -1627,7 +1650,10 @@ mod tests {
16271650
#[test]
16281651
fn test_configure_panic_does_not_publish_state_or_poison_lock() {
16291652
let configuration = RwLock::new(ConfigurationState::default());
1630-
let locators = Arc::new(vec![Arc::new(PanicConfigureLocator) as Arc<dyn Locator>]);
1653+
let panic_locator = Arc::new(PanicConfigureLocator {
1654+
configured_workspace_directories: Mutex::new(None),
1655+
});
1656+
let locators = Arc::new(vec![panic_locator.clone() as Arc<dyn Locator>]);
16311657
let workspace_directories = vec![PathBuf::from("/workspace")];
16321658

16331659
let result = apply_configure_options(
@@ -1650,6 +1676,11 @@ mod tests {
16501676
assert_eq!(state.generation, 0);
16511677
assert!(state.config.workspace_directories.is_none());
16521678
assert!(state.config.conda_executable.is_none());
1679+
assert!(panic_locator
1680+
.configured_workspace_directories
1681+
.lock()
1682+
.unwrap()
1683+
.is_none());
16531684
}
16541685

16551686
#[test]
@@ -1658,9 +1689,12 @@ mod tests {
16581689
let recording_locator = Arc::new(RecordingConfigureLocator {
16591690
configured_workspace_directories: Mutex::new(None),
16601691
});
1692+
let panic_locator = Arc::new(PanicConfigureLocator {
1693+
configured_workspace_directories: Mutex::new(None),
1694+
});
16611695
let locators = Arc::new(vec![
16621696
recording_locator.clone() as Arc<dyn Locator>,
1663-
Arc::new(PanicConfigureLocator) as Arc<dyn Locator>,
1697+
panic_locator.clone() as Arc<dyn Locator>,
16641698
]);
16651699

16661700
let result = apply_configure_options(
@@ -1684,6 +1718,11 @@ mod tests {
16841718
.lock()
16851719
.unwrap()
16861720
.is_none());
1721+
assert!(panic_locator
1722+
.configured_workspace_directories
1723+
.lock()
1724+
.unwrap()
1725+
.is_none());
16871726
}
16881727

16891728
#[test]

0 commit comments

Comments
 (0)