0

I am using the leveldb crate and I am trying to get a Snapshot struct and move this into my struct but I'm having problems as I can't move a referenced value out of a function.

Here is the code for the structs and traits from the crate. I haven't included it here but the Database type implements the Snapshots trait to return a Snapshot:

pub trait Snapshots<K: Key> {
    fn snapshot<'a>(&'a self) -> Snapshot<'a, K>;
}

pub struct Snapshot<'a, K: Key + 'a> {
    raw: RawSnapshot,
    database: &'a Database<K>,
}

And here is my code:

pub struct Cache {
  ldb: RwLock<Database<i32>>
  //other fields omitted
}

pub struct MySnapshot<'a> {
  snapshot: Snapshot<'a, i32>
  //other fields omitted
}

impl Cache {
  pub fn snapshot<'a>(&'a self) -> MySnapshot<'a> {
    let snapshot = self.ldb.read().unwrap().snapshot();
    MySnapshot { snapshot }
  }
}

I think the reason I can't do this (correct me if I'm wrong) is that with RwLock, when I get a reference to the wrapped data (Database in my case), the lifetime of that reference isn't self and so it gets dropped when the RwLock goes out of scope. One possible work around I can think of is removing the RwLock on Database and adding another field:

pub struct Cache {
  ldb: Database<i32>
  //The wrapped type isn't important, just need this to make the function block
  ldb_rwlock: RwLock<u8>
  //other fields omitted
}

impl Cache {
  pub fn snapshot<'a>(&'a self) -> MySnapshot<'a> {
    self.ldb_rwlock.read();
    let snapshot = self.ldb.snapshot();
    MySnapshot { snapshot }
  }
}

This would let me safely get a snapshot as it would block until any write locks are released, and the end user has no way of mutating ldb except through the APIs I implement so as long as I always use the ldb_rwlock before attempting to read/write to ldb it should be safe. However, I feel like this would invalidate the way rust intended RwLock to be used and doesn't feel very idiomatic. Is there any way to resolve this?

Also the reason why I don't wrap ldb in an Arc is because the Cache struct will be wrapped in an Arc and shared across threads, and I only want a lock on the ldb field so that only read/writes to the database requires a lock. I'm still quite new to rust and concurrency in general though, so I'm not sure if what I'm doing is the best way to solve this.

robby10239
  • 31
  • 1
  • 2
  • 2
    `RwLock::read` returns a [`RwLockReadGuard`](https://doc.rust-lang.org/std/sync/struct.RwLockReadGuard.html) instance, which releases read access when dropped. You thus have to keep the read guard returned by `read` "alive" for as long as you need a reference to the value protected by the `RwLock`. Since you want to store a reference to the underlying `Database` (via a `Snapshot`) in `MySnapshot`, you must thus store the `RwLockReadGuard` in `MySnapshot` (hence keeping it alive for the lifetime of that `MySnapshot`), and then access the `Snapshot` via that read guard. – EvilTak Dec 31 '21 at 03:59
  • I've considered that but then that would block for the entire duration a snapshot lives and that would defeat the purpose of a snapshot, which is to allow the current thread to read a snapshot of the database while other threads can continue to write to the database. – robby10239 Dec 31 '21 at 20:37
  • 1
    Something sounds architecturally wrong here. If the snapshot is a read-only view of the data, it shouldn't be guarded by an `RwLock`, it _is_ an `RwLock` or contains one internally. It seems like the locking should be done by the internals of the crate, not by you as the consumer of it. There's also this note at the bottom of the [`Database` docs](https://docs.rs/leveldb/latest/leveldb/database/struct.Database.html): _Multiple Database objects can be kept around, as leveldb synchronises internally._ – bnaecker Jan 01 '22 at 04:38
  • The snapshot is not read only, it's both read and write. It's just that for getting a snapshot I wanted a read lock and then obtain write lock when I needed to write. Although I think you're right about this being architecturally wrong. I missed that part in the docs about being internally synced. However I've decided not to use leveldb for this entirely anyways as only i32 is permitted for keys and that is not viable in my use case. – robby10239 Jan 03 '22 at 15:31

0 Answers0