Skip to content

Commit 0ca29bb

Browse files
committed
fix(deltachat-jsonrpc): block in inner_get_backup_qr
This change avoids the race between `provide_backup` changing the state from NoProvider to Pending and a call to `get_backup_qr` or `get_backup_qr_svg`. With this change `get_backup_qr` and `get_backup_qr_svg` always block until QR code is available, even if `provide_backup` was not called yet.
1 parent cf11741 commit 0ca29bb

File tree

1 file changed

+39
-45
lines changed

1 file changed

+39
-45
lines changed

deltachat-jsonrpc/src/api.rs

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::BTreeMap;
22
use std::sync::Arc;
3+
use std::time::Duration;
34
use std::{collections::HashMap, str::FromStr};
45

56
use anyhow::{anyhow, bail, ensure, Context, Result};
@@ -62,14 +63,14 @@ use crate::api::types::qr::QrObject;
6263
struct AccountState {
6364
/// The Qr code for current [`CommandApi::provide_backup`] call.
6465
///
65-
/// If there currently is a call to [`CommandApi::provide_backup`] this will be
66-
/// `Pending` or `Ready`, otherwise `NoProvider`.
67-
backup_provider_qr: watch::Sender<ProviderQr>,
66+
/// If there is currently is a call to [`CommandApi::provide_backup`] this will be
67+
/// `Some`, otherwise `None`.
68+
backup_provider_qr: watch::Sender<Option<Qr>>,
6869
}
6970

7071
impl Default for AccountState {
7172
fn default() -> Self {
72-
let (tx, _rx) = watch::channel(ProviderQr::NoProvider);
73+
let tx = watch::Sender::new(None);
7374
Self {
7475
backup_provider_qr: tx,
7576
}
@@ -123,21 +124,13 @@ impl CommandApi {
123124
.with_state(account_id, |state| state.backup_provider_qr.subscribe())
124125
.await;
125126

126-
let val: ProviderQr = receiver.borrow_and_update().clone();
127-
match val {
128-
ProviderQr::NoProvider => bail!("No backup being provided"),
129-
ProviderQr::Pending => loop {
130-
if receiver.changed().await.is_err() {
131-
bail!("No backup being provided (account state dropped)");
132-
}
133-
let val: ProviderQr = receiver.borrow().clone();
134-
match val {
135-
ProviderQr::NoProvider => bail!("No backup being provided"),
136-
ProviderQr::Pending => continue,
137-
ProviderQr::Ready(qr) => break Ok(qr),
138-
};
139-
},
140-
ProviderQr::Ready(qr) => Ok(qr),
127+
loop {
128+
if let Some(qr) = receiver.borrow_and_update().clone() {
129+
return Ok(qr);
130+
}
131+
if receiver.changed().await.is_err() {
132+
bail!("No backup being provided (account state dropped)");
133+
}
141134
}
142135
}
143136
}
@@ -1569,32 +1562,39 @@ impl CommandApi {
15691562
/// Returns once a remote device has retrieved the backup, or is cancelled.
15701563
async fn provide_backup(&self, account_id: u32) -> Result<()> {
15711564
let ctx = self.get_context(account_id).await?;
1565+
1566+
let provider = imex::BackupProvider::prepare(&ctx).await?;
15721567
self.with_state(account_id, |state| {
1573-
state.backup_provider_qr.send_replace(ProviderQr::Pending);
1568+
state.backup_provider_qr.send_replace(Some(provider.qr()));
15741569
})
15751570
.await;
15761571

1577-
let provider = imex::BackupProvider::prepare(&ctx).await?;
1572+
let res = provider.await;
1573+
15781574
self.with_state(account_id, |state| {
1579-
state
1580-
.backup_provider_qr
1581-
.send_replace(ProviderQr::Ready(provider.qr()));
1575+
state.backup_provider_qr.send_replace(None);
15821576
})
15831577
.await;
15841578

1585-
provider.await
1579+
res
15861580
}
15871581

15881582
/// Returns the text of the QR code for the running [`CommandApi::provide_backup`].
15891583
///
15901584
/// This QR code text can be used in [`CommandApi::get_backup`] on a second device to
15911585
/// retrieve the backup and setup this second device.
15921586
///
1593-
/// This call will fail if there is currently no concurrent call to
1594-
/// [`CommandApi::provide_backup`]. This call may block if the QR code is not yet
1595-
/// ready.
1587+
/// This call will block until the QR code is ready,
1588+
/// even if there is no concurrent call to [`CommandApi::provide_backup`],
1589+
/// but will fail after 10 seconds to avoid deadlocks.
15961590
async fn get_backup_qr(&self, account_id: u32) -> Result<String> {
1597-
let qr = self.inner_get_backup_qr(account_id).await?;
1591+
let qr = tokio::time::timeout(
1592+
Duration::from_secs(10),
1593+
self.inner_get_backup_qr(account_id),
1594+
)
1595+
.await
1596+
.context("Backup provider did not start in time")?
1597+
.context("Failed to get backup QR code")?;
15981598
qr::format_backup(&qr)
15991599
}
16001600

@@ -1603,14 +1603,20 @@ impl CommandApi {
16031603
/// This QR code can be used in [`CommandApi::get_backup`] on a second device to
16041604
/// retrieve the backup and setup this second device.
16051605
///
1606-
/// This call will fail if there is currently no concurrent call to
1607-
/// [`CommandApi::provide_backup`]. This call may block if the QR code is not yet
1608-
/// ready.
1606+
/// This call will block until the QR code is ready,
1607+
/// even if there is no concurrent call to [`CommandApi::provide_backup`],
1608+
/// but will fail after 10 seconds to avoid deadlocks.
16091609
///
16101610
/// Returns the QR code rendered as an SVG image.
16111611
async fn get_backup_qr_svg(&self, account_id: u32) -> Result<String> {
16121612
let ctx = self.get_context(account_id).await?;
1613-
let qr = self.inner_get_backup_qr(account_id).await?;
1613+
let qr = tokio::time::timeout(
1614+
Duration::from_secs(10),
1615+
self.inner_get_backup_qr(account_id),
1616+
)
1617+
.await
1618+
.context("Backup provider did not start in time")?
1619+
.context("Failed to get backup QR code")?;
16141620
generate_backup_qr(&ctx, &qr).await
16151621
}
16161622

@@ -2141,15 +2147,3 @@ async fn get_config(
21412147
.await
21422148
}
21432149
}
2144-
2145-
/// Whether a QR code for a BackupProvider is currently available.
2146-
#[allow(clippy::large_enum_variant)]
2147-
#[derive(Clone, Debug)]
2148-
enum ProviderQr {
2149-
/// There is no provider, asking for a QR is an error.
2150-
NoProvider,
2151-
/// There is a provider, the QR code is pending.
2152-
Pending,
2153-
/// There is a provider and QR code.
2154-
Ready(Qr),
2155-
}

0 commit comments

Comments
 (0)