(&self)
| 543 | } |
| 544 | |
| 545 | pub async fn reload_vms(&self) -> Result<()> { |
| 546 | let vm_path = self.vm_dir(); |
| 547 | let running_vms = self.supervisor.list().await.context("Failed to list VMs")?; |
| 548 | let running_vms: Vec<(ProcessAnnotation, _)> = running_vms |
| 549 | .into_iter() |
| 550 | .map(|p| (serde_json::from_str(&p.config.note).unwrap_or_default(), p)) |
| 551 | .collect(); |
| 552 | let occupied_cids = running_vms |
| 553 | .iter() |
| 554 | .filter(|(note, _)| note.is_cvm()) |
| 555 | .flat_map(|(_, p)| p.config.cid.map(|cid| (p.config.id.clone(), cid))) |
| 556 | .collect::<HashMap<_, _>>(); |
| 557 | { |
| 558 | let mut state = self.lock(); |
| 559 | for cid in occupied_cids.values() { |
| 560 | state.cid_pool.occupy(*cid)?; |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | // Track VMs with .removing marker — load them but resume cleanup |
| 565 | let mut removing_ids = Vec::new(); |
| 566 | |
| 567 | if vm_path.exists() { |
| 568 | for entry in fs::read_dir(&vm_path).context("Failed to read VM directory")? { |
| 569 | let entry = entry.context("Failed to read directory entry")?; |
| 570 | let vm_path = entry.path(); |
| 571 | if vm_path.is_dir() { |
| 572 | let workdir = VmWorkDir::new(&vm_path); |
| 573 | let is_removing = workdir.is_removing(); |
| 574 | // Load all VMs into memory (including removing ones, so they show in UI) |
| 575 | if let Err(err) = self.load_vm(&vm_path, &occupied_cids, !is_removing).await { |
| 576 | error!("Failed to load VM: {err:?}"); |
| 577 | } |
| 578 | if is_removing { |
| 579 | if let Some(id) = vm_path.file_name().and_then(|n| n.to_str()) { |
| 580 | info!("Found VM {id} with .removing marker, resuming cleanup"); |
| 581 | removing_ids.push(id.to_string()); |
| 582 | } |
| 583 | } |
| 584 | } |
| 585 | } |
| 586 | } |
| 587 | |
| 588 | // Resume cleanup for VMs with .removing marker |
| 589 | for id in removing_ids { |
| 590 | self.spawn_finish_remove(&id); |
| 591 | } |
| 592 | |
| 593 | // Clean up orphaned supervisor processes (in supervisor but not loaded as VMs) |
| 594 | let loaded_vm_ids: HashSet<String> = self.lock().vms.keys().cloned().collect(); |
| 595 | for (_, process) in &running_vms { |
| 596 | if !loaded_vm_ids.contains(&process.config.id) { |
| 597 | info!( |
| 598 | "Cleaning up orphaned supervisor process: {}", |
| 599 | process.config.id |
| 600 | ); |
| 601 | self.spawn_finish_remove(&process.config.id); |
| 602 | } |
no test coverage detected