diff --git a/bindings/matrix-sdk-ffi/changelog.d/6677.fixed.md b/bindings/matrix-sdk-ffi/changelog.d/6677.fixed.md new file mode 100644 index 00000000000..3686e965983 --- /dev/null +++ b/bindings/matrix-sdk-ffi/changelog.d/6677.fixed.md @@ -0,0 +1 @@ +Add methods to make QR login handlers exit cooperatively. diff --git a/bindings/matrix-sdk-ffi/src/qr_code.rs b/bindings/matrix-sdk-ffi/src/qr_code.rs index 96ffc4cef10..e2c7df2c63e 100644 --- a/bindings/matrix-sdk-ffi/src/qr_code.rs +++ b/bindings/matrix-sdk-ffi/src/qr_code.rs @@ -23,6 +23,7 @@ use matrix_sdk::authentication::oauth::{ }; use matrix_sdk_base::crypto::types::qr_login::{self, QrCodeIntent}; use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm, stream::StreamExt}; +use tokio::sync::Notify; use crate::{ authentication::OAuthConfiguration, runtime::get_runtime_handle, task_handle::TaskHandle, @@ -33,11 +34,12 @@ use crate::{ pub struct LoginWithQrCodeHandler { oauth: OAuth, oauth_configuration: OAuthConfiguration, + cancel: Notify, } impl LoginWithQrCodeHandler { pub(crate) fn new(oauth: OAuth, oauth_configuration: OAuthConfiguration) -> Self { - Self { oauth, oauth_configuration } + Self { oauth, oauth_configuration, cancel: Notify::new() } } } @@ -82,15 +84,26 @@ impl LoginWithQrCodeHandler { // We create this task, which will get cancelled once it's dropped, just in case // the progress stream doesn't end. - let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { + let progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { while let Some(state) = progress.next().await { progress_listener.on_update(state.into()); } })); - login.await?; - - Ok(()) + tokio::select! { + // Give priority to cancellation if both updates occur at the same time. + biased; + _ = self.cancel.notified() => { + // Stop forwarding progress to the foreign callback before tearing + // down the handler. + drop(progress_task); + Err(HumanQrLoginError::Cancelled) + } + result = login => { + result?; + Ok(()) + } + } } /// This method allows you to log in by generating a QR code. @@ -126,15 +139,32 @@ impl LoginWithQrCodeHandler { // We create this task, which will get cancelled once it's dropped, just in case // the progress stream doesn't end. - let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { + let progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { while let Some(state) = progress.next().await { progress_listener.on_update(state.into()); } })); - login.await?; + tokio::select! { + // Give priority to cancellation if both updates occur at the same time. + biased; + _ = self.cancel.notified() => { + // Stop forwarding progress to the foreign callback before tearing + // down the handler. + drop(progress_task); + Err(HumanQrLoginError::Cancelled) + } + result = login => { + result?; + Ok(()) + } + } + } - Ok(()) + /// Request the handler to abort cooperatively. This will make the handler + /// tear down its running task and then return the `Cancelled` error. + pub fn abort(&self) { + self.cancel.notify_waiters(); } } @@ -142,11 +172,12 @@ impl LoginWithQrCodeHandler { #[derive(uniffi::Object)] pub struct GrantLoginWithQrCodeHandler { oauth: OAuth, + cancel: Notify, } impl GrantLoginWithQrCodeHandler { pub(crate) fn new(oauth: OAuth) -> Self { - Self { oauth } + Self { oauth, cancel: Notify::new() } } } @@ -182,15 +213,26 @@ impl GrantLoginWithQrCodeHandler { // We create this task, which will get cancelled once it's dropped, just in case // the progress stream doesn't end. - let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { + let progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { while let Some(state) = progress.next().await { progress_listener.on_update(state.into()); } })); - grant.await?; - - Ok(()) + tokio::select! { + // Give priority to cancellation if both updates occur at the same time. + biased; + _ = self.cancel.notified() => { + // Stop forwarding progress to the foreign callback before tearing + // down the handler. + drop(progress_task); + Err(HumanQrGrantLoginError::Cancelled) + } + result = grant => { + result?; + Ok(()) + } + } } /// This method allows you to grant login by generating a QR code. @@ -220,15 +262,32 @@ impl GrantLoginWithQrCodeHandler { // We create this task, which will get cancelled once it's dropped, just in case // the progress stream doesn't end. - let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { + let progress_task = TaskHandle::new(get_runtime_handle().spawn(async move { while let Some(state) = progress.next().await { progress_listener.on_update(state.into()); } })); - grant.await?; + tokio::select! { + // Give priority to cancellation if both updates occur at the same time. + biased; + _ = self.cancel.notified() => { + // Stop forwarding progress to the foreign callback before tearing + // down the handler. + drop(progress_task); + Err(HumanQrGrantLoginError::Cancelled) + } + result = grant => { + result?; + Ok(()) + } + } + } - Ok(()) + /// Request the handler to abort cooperatively. This will make the handler + /// tear down its running task and then return the `Cancelled` error. + pub fn abort(&self) { + self.cancel.notify_waiters(); } }