Skip to content

Commit

Permalink
feat(async): add async_compatible methods to identify backend compa…
Browse files Browse the repository at this point in the history
…tibility (#355)

Add `getters` to warn about `async_incompatible` backends.

Using the type system or fixing all async compatibility issue is big and
structural work.
To avoid runtime crash for our users I suggest using this getter as a
temporary fix.

tracking issue: rustic-rs/rustic#1181 (see for more details)

---------

Co-authored-by: simonsan <[email protected]>
  • Loading branch information
nardoor and simonsan authored Nov 18, 2024
1 parent 27bbbbd commit 37b40e2
Show file tree
Hide file tree
Showing 12 changed files with 92 additions and 4 deletions.
7 changes: 7 additions & 0 deletions crates/backend/src/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,13 @@ impl ReadBackend for LocalBackend {

Ok(vec.into())
}

/// [`LocalBackend`] doesn't use `async`, even under the hood.
///
/// So it can be called from `async` features.
fn is_async_compatible(&self) -> bool {
true
}
}

impl WriteBackend for LocalBackend {
Expand Down
9 changes: 9 additions & 0 deletions crates/backend/src/opendal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,15 @@ impl ReadBackend for OpenDALBackend {
)?
.to_bytes())
}

/// [`OpenDALBackend`] is `sync` and uses `block_on(async Fn)` under the hood.
///
/// When implementing `rustic_core` using this backend in some `async` features will not work.
///
/// see <https://github.com/rustic-rs/rustic/issues/1181>
fn is_async_compatible(&self) -> bool {
false
}
}

impl WriteBackend for OpenDALBackend {
Expand Down
5 changes: 5 additions & 0 deletions crates/backend/src/rclone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,11 @@ impl ReadBackend for RcloneBackend {
) -> RusticResult<Bytes> {
self.rest.read_partial(tpe, id, cacheable, offset, length)
}

/// [`RcloneBackend`] uses [`RestBackend`].
fn is_async_compatible(&self) -> bool {
self.rest.is_async_compatible()
}
}

impl WriteBackend for RcloneBackend {
Expand Down
10 changes: 10 additions & 0 deletions crates/backend/src/rest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,16 @@ impl ReadBackend for RestBackend {
})
.map_err(construct_backoff_error)
}

/// [`RestBackend`] uses `reqwest` which blocking implementation
/// uses an `async` runtime under the hood.
///
/// When implementing `rustic_core` using this backend in some `async` features will not work.
///
/// see <https://github.com/rustic-rs/rustic/issues/1181>
fn is_async_compatible(&self) -> bool {
false
}
}

fn construct_join_url_error(
Expand Down
21 changes: 20 additions & 1 deletion crates/core/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,21 @@ pub trait ReadBackend: Send + Sync + 'static {
fn warm_up(&self, _tpe: FileType, _id: &Id) -> RusticResult<()> {
Ok(())
}

/// Getter to determine if some backend is compatible
/// with async features in `rustic_core` implementations.
///
/// ## Default impl
///
/// A default impl allow these change to be non-breaking.
/// By default it is `false`, async compatibility is opt-in.
///
/// ## Temporary
///
/// see <https://github.com/rustic-rs/rustic/issues/1181>
fn is_async_compatible(&self) -> bool {
false
}
}

/// Trait for Searching in a backend.
Expand Down Expand Up @@ -343,7 +358,7 @@ pub trait WriteBackend: ReadBackend {
mock! {
Backend {}

impl ReadBackend for Backend{
impl ReadBackend for Backend {
fn location(&self) -> String;
fn list_with_size(&self, tpe: FileType) -> RusticResult<Vec<(Id, u32)>>;
fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult<Bytes>;
Expand All @@ -355,6 +370,7 @@ mock! {
offset: u32,
length: u32,
) -> RusticResult<Bytes>;
fn is_async_compatible(&self) -> bool;
}

impl WriteBackend for Backend {
Expand Down Expand Up @@ -400,6 +416,9 @@ impl ReadBackend for Arc<dyn WriteBackend> {
self.deref()
.read_partial(tpe, id, cacheable, offset, length)
}
fn is_async_compatible(&self) -> bool {
self.deref().is_async_compatible()
}
}

impl std::fmt::Debug for dyn WriteBackend {
Expand Down
4 changes: 4 additions & 0 deletions crates/core/src/backend/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ impl ReadBackend for CachedBackend {
fn warm_up(&self, tpe: FileType, id: &Id) -> RusticResult<()> {
self.be.warm_up(tpe, id)
}

fn is_async_compatible(&self) -> bool {
self.be.is_async_compatible()
}
}

impl WriteBackend for CachedBackend {
Expand Down
8 changes: 6 additions & 2 deletions crates/core/src/backend/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ pub trait DecryptWriteBackend: WriteBackend + Clone + 'static {
///
/// # Returns
///
/// The processed data, the original data length and when compression is used, the uncomressed length
/// The processed data, the original data length and when compression is used, the uncompressed length
fn process_data(&self, data: &[u8]) -> RusticResult<(Vec<u8>, u32, Option<NonZeroU32>)>;

/// Writes the given data to the backend without compression and returns the id of the data.
Expand Down Expand Up @@ -559,7 +559,7 @@ impl<C: CryptoKey> DecryptWriteBackend for DecryptBackend<C> {
///
/// # Arguments
///
/// * `extra_echeck` - The compression level to use for zstd.
/// * `extra_verify` - The compression level to use for zstd.
fn set_extra_verify(&mut self, extra_verify: bool) {
self.extra_verify = extra_verify;
}
Expand Down Expand Up @@ -622,6 +622,10 @@ impl<C: CryptoKey> ReadBackend for DecryptBackend<C> {
) -> RusticResult<Bytes> {
self.be.read_partial(tpe, id, cacheable, offset, length)
}

fn is_async_compatible(&self) -> bool {
self.be.is_async_compatible()
}
}

impl<C: CryptoKey> WriteBackend for DecryptBackend<C> {
Expand Down
4 changes: 4 additions & 0 deletions crates/core/src/backend/dry_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ impl<BE: DecryptFullBackend> ReadBackend for DryRunBackend<BE> {
) -> RusticResult<Bytes> {
self.be.read_partial(tpe, id, cacheable, offset, length)
}

fn is_async_compatible(&self) -> bool {
self.be.is_async_compatible()
}
}

impl<BE: DecryptFullBackend> DecryptWriteBackend for DryRunBackend<BE> {
Expand Down
4 changes: 4 additions & 0 deletions crates/core/src/backend/hotcold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ impl ReadBackend for HotColdBackend {
fn warm_up(&self, tpe: FileType, id: &Id) -> RusticResult<()> {
self.be.warm_up(tpe, id)
}

fn is_async_compatible(&self) -> bool {
self.be.is_async_compatible()
}
}

impl WriteBackend for HotColdBackend {
Expand Down
4 changes: 4 additions & 0 deletions crates/core/src/backend/warm_up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ impl ReadBackend for WarmUpAccessBackend {
_ = self.be.read_partial(tpe, id, false, 0, 1);
Ok(())
}

fn is_async_compatible(&self) -> bool {
self.be.is_async_compatible()
}
}

impl WriteBackend for WarmUpAccessBackend {
Expand Down
15 changes: 14 additions & 1 deletion crates/core/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,19 @@ impl<P, S> Repository<P, S> {
pub fn list<T: RepoId>(&self) -> RusticResult<impl Iterator<Item = T>> {
Ok(self.be.list(T::TYPE)?.into_iter().map(Into::into))
}

/// Check if one of this repository backend is incompatible
/// with async features in `rustic_core` implementations.
///
/// see <https://github.com/rustic-rs/rustic/issues/1181>
pub fn is_async_compatible(&self) -> bool {
// check if be or be_hot is incompatible with async
self.be.is_async_compatible()
&& self
.be_hot
.as_ref()
.map_or(true, ReadBackend::is_async_compatible)
}
}

impl<P: ProgressBars, S> Repository<P, S> {
Expand Down Expand Up @@ -1896,7 +1909,7 @@ impl<P: ProgressBars, S: IndexedTree> Repository<P, S> {
impl<P: ProgressBars, S: IndexedIds> Repository<P, S> {
/// Run a backup of `source` using the given options.
///
/// You have to give a preflled [`SnapshotFile`] which is modified and saved.
/// You have to give a prefilled [`SnapshotFile`] which is modified and saved.
///
/// # Arguments
///
Expand Down
5 changes: 5 additions & 0 deletions crates/testing/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ pub mod in_memory_backend {
) -> RusticResult<Bytes> {
Ok(self.0.read().unwrap()[tpe][id].slice(offset as usize..(offset + length) as usize))
}

/// [`InMemoryBackend`] doesn't use `async`, even under the hood.
fn is_async_compatible(&self) -> bool {
true
}
}

impl WriteBackend for InMemoryBackend {
Expand Down

0 comments on commit 37b40e2

Please sign in to comment.