portable-interoperable icon indicating copy to clipboard operation
portable-interoperable copied to clipboard

AsyncSeek

Open nrc opened this issue 3 years ago • 5 comments

An async version of the Seek trait:

pub trait Seek {
    fn seek(&mut self, pos: SeekFrom) -> Result<u64>;

    fn rewind(&mut self) -> Result<()> { ... }
    fn stream_len(&mut self) -> Result<u64> { ... }
    fn stream_position(&mut self) -> Result<u64> { ... }
}

Prior art

futures-rs

pub trait AsyncSeek {
    fn poll_seek(
        self: Pin<&mut Self>, 
        cx: &mut Context<'_>, 
        pos: SeekFrom
    ) -> Poll<Result<u64>>;
}

Tokio

pub trait AsyncSeek {
    fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> Result<()>;

    fn poll_complete(
        self: Pin<&mut Self>, 
        cx: &mut Context<'_>
    ) -> Poll<Result<u64>>;
}

async-std

Extends futures-rs with a convenience seek function

    fn seek(&mut self, pos: SeekFrom) -> ImplFuture<Result<u64>>
    where
        Self: Unpin,
    { ... }

Misc

nrc avatar Mar 21 '22 14:03 nrc

An obvious async-ing of the sync version gives:

pub trait Seek {
    async fn seek(&mut self, pos: SeekFrom) -> Result<u64>;

    async fn rewind(&mut self) -> Result<()> { ... }
    async fn stream_len(&mut self) -> Result<u64> { ... }
    async fn stream_position(&mut self) -> Result<u64> { ... }
}

Everything must be async since the underlying reader might need to read data to fulfil the seek requests.

nrc avatar Mar 21 '22 14:03 nrc

I think it would be easy enough to extend the readiness proposal for read/write to seek. (We'd add a SEEK interest and the async::Seek trait would extend Ready. seek would be a provided method and we'd add a non_blocking_seek required method). I don't think that is super useful though. Seek only makes sense on buffered IO or files and in either case I don't think we have memory requirements that Read has.

nrc avatar Mar 21 '22 14:03 nrc

Note that although we should have Seek for symmetry with sync IO, a read_at/write_at API might make more sense for many users. I'm not sure if we should think about that API now or later, or if one should be implemented in terms of the other.

nrc avatar Mar 21 '22 14:03 nrc

Some issue spelunking around the Tokio design:

To summarise the concern with poll_seek, the scenario is that someone calls the function (possibly a few times) but then stops (i.e., cancels the seek). What should be the effect on the file? And what guarantees can the seek function make about the state of the file. Phrased differently, it is common to expect that a future returning NotReady does not have any side-effects, but a straightforward implementation does not have that property. Splitting the function gets around this question, if a user calls start_seek, they can expect the seek to (probably) happen at some point in the future. seek_complete then just checks whether the seek has happened, and is thus side effect-free in the NotReady case.

I think there are slightly different expectations around async functions, though we must still consider cancellation. I think that as long as an async function is awaited then the user can expect side-effects and there is no expectation of side-effect-free-ness in the event of cancellation, e.g., consider an async read under a completion IO model. So I think that if we get a better story around cancellation then we might be able to do better, but for now, a simple async fn seek seems fine.

nrc avatar Mar 23 '22 13:03 nrc

I found rewind, stream_len and stream_position are not very useful. For the std version, they are often treated as different alternatives to SeekFrom. Do we have scenarios for those APIs?

Xuanwo avatar Jul 28 '22 08:07 Xuanwo