From be11ca8298e61bdbe4b2481768fd0134729960a8 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Sun, 16 Mar 2025 18:22:32 -0300 Subject: [PATCH] prevent race condition --- plugins/http/src/commands.rs | 2 +- plugins/http/src/lib.rs | 15 +++-- plugins/http/src/reqwest_cookie_store.rs | 83 ++++++++++++++++-------- 3 files changed, 66 insertions(+), 34 deletions(-) diff --git a/plugins/http/src/commands.rs b/plugins/http/src/commands.rs index 04d8774f..18953121 100644 --- a/plugins/http/src/commands.rs +++ b/plugins/http/src/commands.rs @@ -266,7 +266,7 @@ pub async fn fetch( #[cfg(feature = "cookies")] { - builder = builder.cookie_provider(Arc::new(state.cookies_jar.clone())); + builder = builder.cookie_provider(state.cookies_jar.clone()); } let mut request = builder.build()?.request(method.clone(), url); diff --git a/plugins/http/src/lib.rs b/plugins/http/src/lib.rs index 432ba510..5acc2b47 100644 --- a/plugins/http/src/lib.rs +++ b/plugins/http/src/lib.rs @@ -23,7 +23,7 @@ const COOKIES_FILENAME: &str = ".cookies"; pub(crate) struct Http { #[cfg(feature = "cookies")] - cookies_jar: crate::reqwest_cookie_store::CookieStoreMutex, + cookies_jar: std::sync::Arc, } pub fn init() -> TauriPlugin { @@ -57,7 +57,7 @@ pub fn init() -> TauriPlugin { let state = Http { #[cfg(feature = "cookies")] - cookies_jar, + cookies_jar: std::sync::Arc::new(cookies_jar), }; app.manage(state); @@ -69,9 +69,14 @@ pub fn init() -> TauriPlugin { if let tauri::RunEvent::Exit = event { let state = app.state::(); - if let Err(_e) = state.cookies_jar.save() { - #[cfg(feature = "tracing")] - tracing::error!("failed to save cookie jar: {_e}"); + match state.cookies_jar.request_save() { + Ok(rx) => { + let _ = rx.recv(); + } + Err(_e) => { + #[cfg(feature = "tracing")] + tracing::error!("failed to save cookie jar: {_e}"); + } } } }) diff --git a/plugins/http/src/reqwest_cookie_store.rs b/plugins/http/src/reqwest_cookie_store.rs index 8ce39ce8..6a7c0186 100644 --- a/plugins/http/src/reqwest_cookie_store.rs +++ b/plugins/http/src/reqwest_cookie_store.rs @@ -5,15 +5,12 @@ // taken from https://github.com/pfernie/reqwest_cookie_store/blob/2ec4afabcd55e24d3afe3f0626ee6dc97bed938d/src/lib.rs use std::{ - fs::File, - io::BufWriter, path::PathBuf, - sync::{Arc, Mutex, MutexGuard, PoisonError}, + sync::{mpsc::Receiver, Mutex}, }; use cookie_store::{CookieStore, RawCookie, RawCookieParseError}; use reqwest::header::HeaderValue; -use serde::{Deserialize, Serialize}; fn set_cookies( cookie_store: &mut CookieStore, @@ -46,10 +43,11 @@ fn cookies(cookie_store: &CookieStore, url: &url::Url) -> Option { /// A [`cookie_store::CookieStore`] wrapped internally by a [`std::sync::Mutex`], suitable for use in /// async/concurrent contexts. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug)] pub struct CookieStoreMutex { pub path: PathBuf, - store: Arc>, + store: Mutex, + save_task: Mutex>, } impl CookieStoreMutex { @@ -57,17 +55,11 @@ impl CookieStoreMutex { pub fn new(path: PathBuf, cookie_store: CookieStore) -> CookieStoreMutex { CookieStoreMutex { path, - store: Arc::new(Mutex::new(cookie_store)), + store: Mutex::new(cookie_store), + save_task: Default::default(), } } - /// Lock and get a handle to the contained [`cookie_store::CookieStore`]. - pub fn lock( - &self, - ) -> Result, PoisonError>> { - self.store.lock() - } - pub fn load( path: PathBuf, reader: R, @@ -76,27 +68,53 @@ impl CookieStoreMutex { .map(|store| CookieStoreMutex::new(path, store)) } - pub fn save(&self) -> cookie_store::Result<()> { - let file = File::create(&self.path)?; - let mut writer = BufWriter::new(file); - let store = self.lock().expect("poisoned cookie jar mutex"); - cookie_store::serde::save(&store, &mut writer, serde_json::to_string) + fn cookies_to_str(&self) -> Result { + let mut cookies = Vec::new(); + for cookie in self + .store + .lock() + .expect("poisoned cookie jar mutex") + .iter_unexpired() + { + if cookie.is_persistent() { + cookies.push(cookie.clone()); + } + } + serde_json::to_string(&cookies) + } + + pub fn request_save(&self) -> cookie_store::Result> { + let cookie_str = self.cookies_to_str()?; + let path = self.path.clone(); + let (tx, rx) = std::sync::mpsc::channel(); + let task = tauri::async_runtime::spawn(async move { + match tokio::fs::write(&path, &cookie_str).await { + Ok(()) => { + let _ = tx.send(()); + } + Err(_e) => { + #[cfg(feature = "tracing")] + tracing::error!("failed to save cookie jar: {_e}"); + } + } + }); + self.save_task + .lock() + .unwrap() + .replace(CancellableTask(task)); + Ok(rx) } } impl reqwest::cookie::CookieStore for CookieStoreMutex { fn set_cookies(&self, cookie_headers: &mut dyn Iterator, url: &url::Url) { - let mut store = self.store.lock().unwrap(); - set_cookies(&mut store, cookie_headers, url); + set_cookies(&mut self.store.lock().unwrap(), cookie_headers, url); // try to persist cookies immediately asynchronously - let cookies_jar = self.clone(); - tauri::async_runtime::spawn(async move { - if let Err(_e) = cookies_jar.save() { - #[cfg(feature = "tracing")] - tracing::error!("failed to save cookie jar: {_e}"); - } - }); + if let Err(_e) = self.request_save() { + #[cfg(feature = "tracing")] + tracing::error!("failed to save cookie jar: {_e}"); + } } fn cookies(&self, url: &url::Url) -> Option { @@ -104,3 +122,12 @@ impl reqwest::cookie::CookieStore for CookieStoreMutex { cookies(&store, url) } } + +#[derive(Debug)] +struct CancellableTask(tauri::async_runtime::JoinHandle<()>); + +impl Drop for CancellableTask { + fn drop(&mut self) { + self.0.abort(); + } +}