0

I've got a small RPC network protocol I've written, and there's a server-streaming method (very similar to gRPC style server-streaming) called range. The client sends start and end keys, and the server streams the values back. I'm using the tower crate as an interface in between the network protocol library and the application code that actually implements an instance of this service. I'd like to use the nice rocksdb wrapper that exists for Rust, but I'm running into lifetime issues. In the short term since I couldn't figure them out, I decided to use sled. That worked great. However, I'd prefer to use RocksDB if I can since sled still markets itself as a beta database.

Here's a distilled version of the code involved in the problem:

use std::ops::Range;

use futures_util::{Stream, Future, future::{Ready, ready}, stream};
use rocksdb::{ReadOptions, IteratorMode, Error, DB, DBIterator};

// Service is more complicated than this, but here are the important bits
pub trait Service {
    type RequestFuture: Future<Output = Self::RequestStream>;
    type RequestStream: Stream<Item = Self::RequestItem>;
    type RequestItem;

    fn range(&self, range: Range<Vec<u8>>) -> Self::RequestFuture;
}

pub async fn network_protocol_code<T: Service>(service: T) 
where
    T::RequestStream: Send + 'static,
    T::RequestItem: Send + 'static
{
    // use service to send responses to the client
}

pub struct ExampleService {
    database: DB
}

impl Service for ExampleService {
    type RequestFuture = Ready<Self::RequestStream>;
    type RequestStream = stream::Iter<DBIterator<'static>>;
    // The actual `RequestItem` isn't actually both the key and the value, it's just a `Box<[u8]>`.
    type RequestItem = Result<(Box<[u8]>, Box<[u8]>), Error>;

    fn range(&self, range: Range<Vec<u8>>) -> Self::RequestFuture {
        let mut options = ReadOptions::default();
        options.set_iterate_range(range);
        let iterator = self.database.iterator_opt(IteratorMode::Start, options);
        let stream = stream::iter(iterator);

        ready(stream)
    }
}

The error I get is this:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/lib.rs:35:38
   |
32 |     fn range(&self, range: Range<Vec<u8>>) -> Self::RequestFuture {
   |              ----- this data with an anonymous lifetime `'_`...
...
35 |         let iterator = self.database.iterator_opt(IteratorMode::Start, options);
   |                        ------------- ^^^^^^^^^^^^
   |                        |
   |                        ...is used here...
...
38 |         ready(stream)
   |         ------------- ...and is required to live as long as `'static` here
   |
note: `'static` lifetime requirement introduced by the return type
  --> src/lib.rs:32:47
   |
32 |     fn range(&self, range: Range<Vec<u8>>) -> Self::RequestFuture {
   |                                               ^^^^^^^^^^^^^^^^^^^ requirement introduced by this return type
...
38 |         ready(stream)
   |         ------------- because of this returned expression

For more information about this error, try `rustc --explain E0759`.

I understand to mean that it can't return a 'static reference since the anonymous lifetime on self isn't 'static, but shouldn't it be possible to return a 'static Stream from an iterator like this? The first thing I tried was to get rid of that trait bound requirement in the network protocol code, but rustc practically dictated that the Stream be Send + 'static. This is because a new task gets spawned using tokio::spawn and the trait bounds there require that the Future is Send and 'static.

I've recently read Common Rust Lifetime Misconceptions, specifically this section. I understand that to mean that the Stream returned here doesn't have to valid for the lifetime of the program, only until the network protocol code is finished writing it. Is it possible to get a Send + 'static stream out of DBIterator somehow? How could I go about it?

broughjt
  • 73
  • 4
  • The stream still refers to the iterator, which refers to `self.database`. I think you need to `collect()` the iterator's items. – Chayim Friedman Sep 14 '22 at 23:35
  • Right, that's what I've been thinking too is that the iterator refers to `self.database`. Looking in the source code it actually stores a raw pointer and uses `PhantomData` to appear to the type system that it has a type generic based on the type of `DB` you used to create it. https://docs.rs/rocksdb/latest/src/rocksdb/db_iterator.rs.html#74 A client could potentially request hundreds of thousands of keys from the server and so allocating a buffer to hold them all contiguously is going to be a performance problem. The idea would be to use the iterator directly without buffering. – broughjt Sep 15 '22 at 19:49
  • I don't think you can do that. An iterator object is not a stream you can send over the network. You should loop over the keys using the iterator and stream them out – Asad Awadia Sep 16 '22 at 02:01

0 Answers0