fix(offline-cache): run the secret-vault open off the async pool (no zbus panic)
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
This commit is contained in:
2 files changed
+26
-13
No files matched your search
@@ -320,16 +320,27 @@ pub(crate) async fn try_cmaf_offline_download(
|
||||
.await?
|
||||
};
|
||||
|
||||
// Open (or lazily init) the secret vault and wrap the keying material
|
||||
// before it touches the filesystem.
|
||||
let vault = crate::secret_vault::get_or_init(&offline_root_path)
|
||||
.map_err(|e| format!("SecretBox init failed: {}", e))?;
|
||||
let content_key_wrapped = vault
|
||||
.wrap(&bundle.content_key)
|
||||
.map_err(|e| format!("Failed to wrap content_key: {}", e))?;
|
||||
let infos_wrapped = vault
|
||||
.wrap(bundle.infos.as_bytes())
|
||||
.map_err(|e| format!("Failed to wrap infos: {}", e))?;
|
||||
// Open the secret vault and wrap the keying material on a BLOCKING thread.
|
||||
// The OS keyring (secret-service) does a synchronous D-Bus round-trip via
|
||||
// zbus, which PANICS ("cannot start a runtime from within a runtime") when
|
||||
// run on an async worker. spawn_blocking moves it off the async pool — the
|
||||
// same reason the playback decrypt path already uses spawn_blocking.
|
||||
let offline_root_for_vault = offline_root_path.clone();
|
||||
let content_key = bundle.content_key;
|
||||
let infos = bundle.infos.clone();
|
||||
let (content_key_wrapped, infos_wrapped) = tokio::task::spawn_blocking(move || {
|
||||
let vault = crate::secret_vault::get_or_init(&offline_root_for_vault)
|
||||
.map_err(|e| format!("SecretBox init failed: {}", e))?;
|
||||
let ck = vault
|
||||
.wrap(&content_key)
|
||||
.map_err(|e| format!("Failed to wrap content_key: {}", e))?;
|
||||
let inf = vault
|
||||
.wrap(infos.as_bytes())
|
||||
.map_err(|e| format!("Failed to wrap infos: {}", e))?;
|
||||
Ok::<(Vec<u8>, Vec<u8>), String>((ck, inf))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("vault task join failed: {}", e))??;
|
||||
|
||||
// Persist the encrypted bundle to disk.
|
||||
let (layout, total_bytes) =
|
||||
|
||||
@@ -19,9 +19,11 @@ static VAULT: OnceLock<std::sync::Mutex<Option<SecretBox>>> = OnceLock::new();
|
||||
/// 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));
|
||||
let mut guard = cell
|
||||
.lock()
|
||||
.map_err(|_| SecretError::Other("secret vault mutex poisoned".to_string()))?;
|
||||
// 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());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user