| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // All interactions with dlcservice is wrapped here. This includes both |
| // sending D-BUS methods and responding to signals. |
| |
| use super::signal; |
| use crate::dbus_constants::dlc_service; |
| use crate::dbus_wrapper::DbusConnectionTrait; |
| use crate::shader_cache_mount::ShaderCacheMountMapPtr; |
| use crate::{common::*, dlc_queue::DlcQueuePtr}; |
| |
| use anyhow::{anyhow, Result}; |
| use log::{debug, error, info, warn}; |
| use std::collections::HashSet; |
| use std::sync::Arc; |
| use system_api::{ |
| dlcservice::dlc_state::State, dlcservice::DlcState, dlcservice::InstallRequest, |
| shadercached::ShaderCacheMountStatus, |
| }; |
| |
| pub async fn handle_dlc_state_changed<D: DbusConnectionTrait>( |
| raw_bytes: Vec<u8>, |
| mount_map: ShaderCacheMountMapPtr, |
| dlc_queue: DlcQueuePtr, |
| dbus_conn: Arc<D>, |
| ) { |
| // If shader cache DLC was installed, mount the DLC to a MountPoint that |
| // wants this DLC. |
| let dlc_state: DlcState = protobuf::Message::parse_from_bytes(&raw_bytes).unwrap(); |
| debug!( |
| "DLC state changed: {}, {:?}, {}", |
| dlc_state.id, |
| dlc_state.state.enum_value(), |
| dlc_state.progress |
| ); |
| if let Ok(steam_app_id) = dlc_to_steam_app_id(&dlc_state.id) { |
| // Note that INSTALLING and INSTALLED messages can be sent out of order |
| // if the installation was very fast. Hence, INSTALLING state is |
| // ignored. |dlc_queue.add_installing| is added during |
| // |dlc_queue.next_to_install| inside |periodic_dlc_handler|. |
| if dlc_state.state.enum_value() == Ok(State::INSTALLED) { |
| info!("Shader cache DLC installed"); |
| let mut dlc_queue = dlc_queue.write().await; |
| dlc_queue.remove_installing(&steam_app_id); |
| debug!( |
| "ShaderCache DLC for {} installed, mounting if required", |
| steam_app_id |
| ); |
| if let Err(e) = mount_dlc(steam_app_id, mount_map, dbus_conn.clone()).await { |
| warn!("Mount failed, {}", e); |
| } |
| } else if dlc_state.state.enum_value() == Ok(State::NOT_INSTALLED) { |
| info!("Shader cache DLC failed to install"); |
| debug!("Failed to install DLC for {}", steam_app_id); |
| let mut dlc_queue = dlc_queue.write().await; |
| if !dlc_queue.remove_installing(&steam_app_id) { |
| warn!("DLC failed to install, but it was not found in installing set"); |
| } |
| warn!("Clearing mount requests for the failed DLC"); |
| if let Err(e) = dequeue_mount_for_failed_dlc( |
| steam_app_id, |
| "dlc could not be installed", |
| mount_map.clone(), |
| dbus_conn.clone(), |
| ) |
| .await |
| { |
| warn!("Failed to notify failed mounts {}", e); |
| } |
| } else if dlc_state.state.enum_value() == Ok(State::INSTALLING) { |
| debug!("Shader cache DLC install progress: {}", dlc_state.progress); |
| } |
| } |
| } |
| |
| async fn dequeue_mount_for_failed_dlc<D: DbusConnectionTrait>( |
| steam_app_id: SteamAppId, |
| error: &str, |
| mount_map: ShaderCacheMountMapPtr, |
| dbus_conn: Arc<D>, |
| ) -> Result<()> { |
| let mut mount_map = mount_map.write().await; |
| let mut mount_status_to_send: Vec<ShaderCacheMountStatus> = vec![]; |
| |
| for (vm_id, shader_cache_mount) in mount_map.iter_mut() { |
| if !shader_cache_mount.is_pending_mount(&steam_app_id) { |
| continue; |
| } |
| |
| shader_cache_mount.dequeue_mount(&steam_app_id); |
| let mut mount_status = ShaderCacheMountStatus::new(); |
| mount_status.mounted = false; |
| mount_status.vm_name = vm_id.vm_name.clone(); |
| mount_status.vm_owner_id = vm_id.vm_owner_id.clone(); |
| mount_status.steam_app_id = steam_app_id; |
| mount_status.error = format!("Mount not attempted {:?}: {}", vm_id, error); |
| mount_status_to_send.push(mount_status); |
| } |
| signal::signal_mount_status(mount_status_to_send, dbus_conn) |
| } |
| |
| pub async fn periodic_dlc_handler<D: DbusConnectionTrait>( |
| mount_map: ShaderCacheMountMapPtr, |
| dlc_queue: DlcQueuePtr, |
| dbus_conn: Arc<D>, |
| ) { |
| debug!("Dlc queue status {}", dlc_queue.read().await); |
| |
| loop { |
| // Handle install queue |
| if !dlc_queue.read().await.available_to_install_more_dlc() { |
| break; |
| } |
| |
| let maybe_steam_app_id = dlc_queue.write().await.next_to_install(); |
| if maybe_steam_app_id.is_none() { |
| break; |
| } |
| let steam_app_id = maybe_steam_app_id.unwrap(); |
| |
| info!("Attempting shader cache DLC install"); |
| let result = install_shader_cache_dlc(steam_app_id, dbus_conn.clone()).await; |
| if result.is_ok() { |
| info!("Started shader cache DLC install"); |
| debug!("Started shader cache DLC install: {}", steam_app_id); |
| // Successfully queued install |
| continue; |
| } |
| |
| if let Err(e) = result { |
| warn!("Failed to install shader cache DLC: {}", e); |
| } |
| |
| // Don't retry to install dlc again, there are retries from |
| // the VM side in various points of UX. |
| // Simply just remove from installing set and try next. |
| dlc_queue.write().await.remove_installing(&steam_app_id); |
| |
| // If mounting was queued, remove it. |
| if let Err(e) = dequeue_mount_for_failed_dlc( |
| steam_app_id, |
| "dlc could not be installed", |
| mount_map.clone(), |
| dbus_conn.clone(), |
| ) |
| .await |
| { |
| error!("Failed to dequeue failed install: {}", e); |
| } |
| // Attempt to install next by continuing the loop |
| } |
| |
| let mut failed_uninstalls: HashSet<SteamAppId> = HashSet::new(); |
| loop { |
| // Handle uninstall queue, attempt to uninstall everything |
| let maybe_steam_app_id = dlc_queue.write().await.next_to_uninstall(); |
| if maybe_steam_app_id.is_none() { |
| break; |
| } |
| let steam_app_id = maybe_steam_app_id.unwrap(); |
| |
| debug!("Uninstalling shader cache for {}", steam_app_id); |
| if let Err(e) = unmount_dlc(steam_app_id, mount_map.clone()).await { |
| warn!("Failed to unmount: {}", e); |
| failed_uninstalls.insert(steam_app_id); |
| continue; |
| } |
| if let Err(e) = mount_map |
| .wait_unmount_completed(Some(steam_app_id), UNMOUNTER_INTERVAL * 2) |
| .await |
| { |
| warn!("Failed to wait for unmount: {}", e); |
| failed_uninstalls.insert(steam_app_id); |
| continue; |
| } |
| if let Err(e) = uninstall_shader_cache_dlc(steam_app_id, dbus_conn.clone()).await { |
| // DLC uninstallation can fail if DLC is missing or transient |
| // failures. |
| // TODO(b/285965527): Retry shader dlc uninstallation on dlc missing |
| // failure |
| warn!("Failed to uninstall DLC"); |
| debug!( |
| "Failed to uninstall DLC: {}, not attempting to uninstall", |
| e |
| ); |
| continue; |
| } |
| } |
| |
| if !failed_uninstalls.is_empty() { |
| debug!( |
| "Queueing {:?} to uninstall (failed this round)", |
| failed_uninstalls |
| ); |
| dlc_queue |
| .write() |
| .await |
| .queue_uninstall_multi(&failed_uninstalls); |
| } |
| } |
| |
| pub async fn mount_dlc<D: DbusConnectionTrait>( |
| steam_app_id: SteamAppId, |
| mount_map: ShaderCacheMountMapPtr, |
| dbus_conn: Arc<D>, |
| ) -> Result<()> { |
| // Iterate through all mount points then attempt to mount shader cache if |
| // |target_steam_app_id| matches |steam_app_id_to_mount| (which was just |
| // installed) |
| let mut mount_map = mount_map.write().await; |
| let mut errors: Vec<String> = vec![]; |
| let mut mount_status_to_send: Vec<ShaderCacheMountStatus> = vec![]; |
| |
| for (vm_id, shader_cache_mount) in mount_map.iter_mut() { |
| if shader_cache_mount.is_pending_mount(&steam_app_id) { |
| info!("Mounting DLC"); |
| debug!("Mounting {:?} for {:?}", steam_app_id, vm_id); |
| let mount_result = shader_cache_mount |
| .setup_mount_destination(vm_id, steam_app_id, dbus_conn.clone()) |
| .await |
| .and_then(|_| shader_cache_mount.bind_mount_dlc(steam_app_id)) |
| .and_then(|_| shader_cache_mount.add_game_to_db_list(steam_app_id)); |
| |
| let mut mount_status = ShaderCacheMountStatus::new(); |
| mount_status.mounted = mount_result.is_ok(); |
| mount_status.vm_name = vm_id.vm_name.clone(); |
| mount_status.vm_owner_id = vm_id.vm_owner_id.clone(); |
| mount_status.steam_app_id = steam_app_id; |
| if let Err(e) = mount_result { |
| errors.push(format!("Failed to mount {:?}, {:?}", vm_id, e)); |
| mount_status.error = e.to_string(); |
| } |
| mount_status_to_send.push(mount_status); |
| } |
| } |
| |
| if let Err(e) = signal::signal_mount_status(mount_status_to_send, dbus_conn.clone()) { |
| errors.push(e.to_string()); |
| } |
| |
| if errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(anyhow!("{:?}", errors)) |
| } |
| } |
| |
| pub async fn unmount_dlc( |
| steam_app_id_to_unmount: SteamAppId, |
| mount_map: ShaderCacheMountMapPtr, |
| ) -> Result<()> { |
| info!("Unmounting DLC"); |
| // Iterate through all mount points then queue unmount for |
| // |steam_app_id_to_unmount| |
| { |
| // |mount_map| with write mutex needs to go out of scope after this |
| // loop so that background unmounter can take the mutex |
| let mut mount_map = mount_map.write().await; |
| for (vm_id, shader_cache_mount) in mount_map.iter_mut() { |
| debug!( |
| "Processing DLC {} unmount for VM {:?}", |
| steam_app_id_to_unmount, vm_id |
| ); |
| shader_cache_mount.remove_game_from_db_list(steam_app_id_to_unmount)?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| pub async fn install_shader_cache_dlc<D: DbusConnectionTrait>( |
| steam_game_id: SteamAppId, |
| dbus_conn: Arc<D>, |
| ) -> Result<()> { |
| let dlc_name = steam_app_id_to_dlc(steam_game_id); |
| debug!("Requesting to install dlc {}", dlc_name); |
| |
| let mut install_request = InstallRequest::new(); |
| install_request.id = dlc_name; |
| let install_request_bytes = protobuf::Message::write_to_bytes(&install_request)?; |
| dbus_conn |
| .call_dbus_method( |
| dlc_service::SERVICE_NAME, |
| dlc_service::PATH_NAME, |
| dlc_service::INTERFACE_NAME, |
| dlc_service::INSTALL_METHOD, |
| (install_request_bytes,), |
| ) |
| .await?; |
| |
| Ok(()) |
| } |
| |
| pub async fn uninstall_shader_cache_dlc<D: DbusConnectionTrait>( |
| steam_game_id: SteamAppId, |
| dbus_conn: Arc<D>, |
| ) -> Result<()> { |
| let dlc_name = steam_app_id_to_dlc(steam_game_id); |
| debug!("Requesting to uninstall dlc {}", dlc_name); |
| dbus_conn |
| .call_dbus_method( |
| dlc_service::SERVICE_NAME, |
| dlc_service::PATH_NAME, |
| dlc_service::INTERFACE_NAME, |
| dlc_service::UNINSTALL_METHOD, |
| (dlc_name,), |
| ) |
| .await?; |
| Ok(()) |
| } |
| |
| pub async fn uninstall_all_shader_cache_dlcs<D: DbusConnectionTrait>( |
| dbus_conn: Arc<D>, |
| ) -> Result<()> { |
| let (installed_ids,): (Vec<String>,) = dbus_conn |
| .call_dbus_method( |
| dlc_service::SERVICE_NAME, |
| dlc_service::PATH_NAME, |
| dlc_service::INTERFACE_NAME, |
| dlc_service::GET_INSTALLED_METHOD, |
| (), |
| ) |
| .await?; |
| for dlc_id in installed_ids { |
| if let Ok(steam_game_id) = dlc_to_steam_app_id(&dlc_id) { |
| uninstall_shader_cache_dlc(steam_game_id, dbus_conn.clone()).await?; |
| } |
| } |
| |
| Ok(()) |
| } |