The CMAF download opened the SecretBox (OS keyring -> secret-service -> a
synchronous zbus D-Bus round-trip) directly on a tokio async worker, which
panicked ("Cannot start a runtime from within a runtime") and poisoned the
vault mutex — every later attempt then failed and fell back to the legacy
plain-FLAC path. Playback never hit this because load_cmaf_bundle already runs
inside spawn_blocking.
- downloader: wrap the vault open + key wrap in tokio::task::spawn_blocking
(off the async pool), mirroring the playback decrypt path.
- secret_vault: get_or_init recovers a poisoned mutex (into_inner) instead of
wedging the vault for the rest of the session.
Verified at runtime: CMAF bundle now persists (tracks-cmaf/<id>/) and plays
back decrypted; no panic, no legacy fallback.
cc
38 lines
1.5 KiB
Rust
38 lines
1.5 KiB
Rust
//! Lazy singleton access to the `qbz-secrets::SecretBox` for the offline
|
|
//! cache.
|
|
//!
|
|
//! The vault is opened on first use (with the OS-keyring + KDF-fallback
|
|
//! selection happening inside `qbz-secrets`) and then shared across all
|
|
//! offline-cache calls. The storage directory is the app config dir — the
|
|
//! install UUID for the KDF fallback lives there so it survives upgrades.
|
|
|
|
use std::path::Path;
|
|
use std::sync::OnceLock;
|
|
|
|
use qbz_secrets::{SecretBox, SecretError};
|
|
|
|
const SERVICE_NAME: &str = "qbz";
|
|
|
|
static VAULT: OnceLock<std::sync::Mutex<Option<SecretBox>>> = OnceLock::new();
|
|
|
|
/// Get-or-init the vault. `storage_dir` is used only on the first call;
|
|
/// subsequent calls return the cached handle regardless.
|
|
pub fn get_or_init(storage_dir: &Path) -> Result<SecretBox, SecretError> {
|
|
let cell = VAULT.get_or_init(|| std::sync::Mutex::new(None));
|
|
// Recover from a poisoned mutex rather than wedging the vault for the rest
|
|
// of the session: a panic during a prior `SecretBox::open` (e.g. the zbus
|
|
// "runtime within a runtime" panic this guards against) leaves the inner
|
|
// `Option` as `None`, so recovering simply retries the open.
|
|
let mut guard = cell.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
if let Some(existing) = guard.as_ref() {
|
|
return Ok(existing.clone());
|
|
}
|
|
let vault = SecretBox::open(SERVICE_NAME, storage_dir)?;
|
|
log::info!(
|
|
"[OfflineCache/Vault] Opened SecretBox backend={:?}",
|
|
vault.backend_kind()
|
|
);
|
|
*guard = Some(vault.clone());
|
|
Ok(vault)
|
|
}
|