fix(http): use tokio oneshot channel for detecting abort (#1395)

* fix(http): properly handle aborting

closes #1376

* abort early in JS

* avoid using unnecessary mutexes

* fix lint

* update bundle

* simplify
This commit is contained in:
Amr Bashir
2024-07-08 20:31:29 +03:00
committed by GitHub
parent b07c092cd2
commit ac9a25cc12
6 changed files with 109 additions and 34 deletions
+64 -25
View File
@@ -11,8 +11,9 @@ use tauri::{
async_runtime::Mutex,
command,
ipc::{CommandScope, GlobalScope},
Manager, ResourceId, Runtime, State, Webview,
Manager, ResourceId, ResourceTable, Runtime, State, Webview,
};
use tokio::sync::oneshot::{channel, Receiver, Sender};
use crate::{
scope::{Entry, Scope},
@@ -22,20 +23,47 @@ use crate::{
const HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
struct ReqwestResponse(reqwest::Response);
impl tauri::Resource for ReqwestResponse {}
type CancelableResponseResult = Result<Result<reqwest::Response>>;
type CancelableResponseResult = Result<reqwest::Response>;
type CancelableResponseFuture =
Pin<Box<dyn Future<Output = CancelableResponseResult> + Send + Sync>>;
struct FetchRequest(Mutex<CancelableResponseFuture>);
impl FetchRequest {
fn new(f: CancelableResponseFuture) -> Self {
Self(Mutex::new(f))
struct FetchRequest {
fut: Mutex<CancelableResponseFuture>,
abort_tx_rid: ResourceId,
abort_rx_rid: ResourceId,
}
impl tauri::Resource for FetchRequest {}
struct AbortSender(Sender<()>);
impl tauri::Resource for AbortRecveiver {}
impl AbortSender {
fn abort(self) {
let _ = self.0.send(());
}
}
impl tauri::Resource for FetchRequest {}
impl tauri::Resource for ReqwestResponse {}
struct AbortRecveiver(Receiver<()>);
impl tauri::Resource for AbortSender {}
trait AddRequest {
fn add_request(&mut self, fut: CancelableResponseFuture) -> ResourceId;
}
impl AddRequest for ResourceTable {
fn add_request(&mut self, fut: CancelableResponseFuture) -> ResourceId {
let (tx, rx) = channel::<()>();
let (tx, rx) = (AbortSender(tx), AbortRecveiver(rx));
let req = FetchRequest {
fut: Mutex::new(fut),
abort_tx_rid: self.add(tx),
abort_rx_rid: self.add(rx),
};
self.add(req)
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
@@ -239,9 +267,9 @@ pub async fn fetch<R: Runtime>(
request = request.body(data);
}
let fut = async move { Ok(request.send().await.map_err(Into::into)) };
let fut = async move { request.send().await.map_err(Into::into) };
let mut resources_table = webview.resources_table();
let rid = resources_table.add(FetchRequest::new(Box::pin(fut)));
let rid = resources_table.add_request(Box::pin(fut));
Ok(rid)
} else {
@@ -260,9 +288,9 @@ pub async fn fetch<R: Runtime>(
.header(header::CONTENT_TYPE, data_url.mime_type().to_string())
.body(reqwest::Body::from(body))?;
let fut = async move { Ok(Ok(reqwest::Response::from(response))) };
let fut = async move { Ok(reqwest::Response::from(response)) };
let mut resources_table = webview.resources_table();
let rid = resources_table.add(FetchRequest::new(Box::pin(fut)));
let rid = resources_table.add_request(Box::pin(fut));
Ok(rid)
}
_ => Err(Error::SchemeNotSupport(scheme.to_string())),
@@ -270,14 +298,13 @@ pub async fn fetch<R: Runtime>(
}
#[command]
pub async fn fetch_cancel<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::Result<()> {
let req = {
let resources_table = webview.resources_table();
resources_table.get::<FetchRequest>(rid)?
};
let mut req = req.0.lock().await;
*req = Box::pin(async { Err(Error::RequestCanceled) });
pub fn fetch_cancel<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::Result<()> {
let mut resources_table = webview.resources_table();
let req = resources_table.get::<FetchRequest>(rid)?;
let abort_tx = resources_table.take::<AbortSender>(req.abort_tx_rid)?;
if let Some(abort_tx) = Arc::into_inner(abort_tx) {
abort_tx.abort();
}
Ok(())
}
@@ -286,14 +313,26 @@ pub async fn fetch_send<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
) -> crate::Result<FetchResponse> {
let req = {
let (req, abort_rx) = {
let mut resources_table = webview.resources_table();
resources_table.take::<FetchRequest>(rid)?
let req = resources_table.get::<FetchRequest>(rid)?;
let abort_rx = resources_table.take::<AbortRecveiver>(req.abort_rx_rid)?;
(req, abort_rx)
};
let res = match req.0.lock().await.as_mut().await {
Ok(Ok(res)) => res,
Ok(Err(e)) | Err(e) => return Err(e),
let Some(abort_rx) = Arc::into_inner(abort_rx) else {
return Err(Error::RequestCanceled);
};
let mut fut = req.fut.lock().await;
let res = tokio::select! {
res = fut.as_mut() => res?,
_ = abort_rx.0 => {
let mut resources_table = webview.resources_table();
resources_table.close(rid)?;
return Err(Error::RequestCanceled);
}
};
let status = res.status();