Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asynchronous Seek and Seek implementation for File #785

Closed
wants to merge 9 commits into from

Conversation

agrif
Copy link

@agrif agrif commented Nov 30, 2018

(Original issue: #776)

Motivation

AsyncRead and AsyncWrite provide a way to do asynchronous I/O without caring about the specific underlying implementation. No such interface exists for seeking, though.

Solution

I implemented AsyncSeek, modeled after AsyncRead and AsyncWrite. Like those, it comes with a default implementation that relies on the underlying Seek implementation to use WouldBlock errors.

I also added tokio_io::io::seek, a future-flavored seek call built on top of AsyncSeek. The future it returns is almost an exact copy of SeekFuture from tokio_fs, but I kept both structs around since one has a type argument and one does not.

Finally, I implemented AsyncSeek on File, and changed the existing test to use the new io::seek. The methods seek and poll_seek on File (as well as SeekFuture) should probably be deprecated, but I'm not sure how that works.

@tobz
Copy link
Member

tobz commented Dec 1, 2018

That was a fast turnaround from issue to PR! 👍

I'll try and take a look at this today. WRT your File::seek/File::poll_seek comment, I think we can deprecate/hide them from the docs like this:

#[doc(hidden)]
#[deprecated(note="something here about tokio::io::seek and AsyncSeek")]

We definitely can't delete them yet, for obvious backwards-compatibility reasons, but we should definitely push people towards the better path if possible.

@agrif
Copy link
Author

agrif commented Dec 1, 2018

I was mostly confused about a version field in the deprecated attribute, since I see one included on every other deprecated interface but don't really have any idea how that is decided. Code is pretty straightforward, but project management stuff like versions is complicated :D

@tobz
Copy link
Member

tobz commented Dec 1, 2018

Yeah, since is optional.

I'd say let's add the above, and set since to 0.2 which is what we'll be at after a minor version bump. I'm pretty sure @carllerche will be cool with that, given that we're deprecating bits.

@agrif
Copy link
Author

agrif commented Dec 1, 2018

I have deprecated the File methods and SeekFuture, and sprinkled #[allow(deprecated) as required. If we gotta change the version, let me know (that's easy).

@tobz
Copy link
Member

tobz commented Dec 1, 2018

Actually, now that I've gone and looked at it.... I say we should make the version be 0.1.5.

0.1.5 will be when we release this, and 0.2 will be when we delete the deprecated methods.

Sorry about the back and forth!

Copy link
Member

@tobz tobz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few minimal nits, but had some thoughts overall....

So, AsyncRead inherits Read, and AsyncWrite inherits Write, but this is something that will change in the future with a breaking release. It was a mistake to inherit them, and ideally, we want async-only types to only implement asynchronous public methods.

Given that impl Seek for File only works when run via an executor, it feels like we should drop that impl, and only implement AsyncSeek for File.

The tricky part is that we still want the behavior of ::would_block because the threadpool may actually run out of blocking threads to spawn on to, so we have to account for that.

tokio-io/src/async_seek.rs Outdated Show resolved Hide resolved
tokio-io/src/async_seek.rs Outdated Show resolved Hide resolved
@agrif
Copy link
Author

agrif commented Dec 2, 2018

I'm happy to give it a go, but I'm a little bit confused about what the future breaking release would look like. Right now it looks like you need to implement Read to be able to rely on a ton of infrastructure from std, like Take, Chain, BufReader, BufRead, etc. Would you be re-implementing those in tokio? Or perhaps changing std to allow re-using those without Read?

I don't think there is any similar infrastructure for Seek, but I'll at least poke around for a bit before making these changes.

@tobz
Copy link
Member

tobz commented Dec 2, 2018

I'm happy to give it a go, but I'm a little bit confused about what the future breaking release would look like.

Sorry, mostly that in the pre-1.0 world, we've been treating minor releases as our way to distinguish breaking updates. Since we're in 0.1 now, we would do this new method/deprecate old stuff as a patch version, then eventually drop the deprecated methods in a minor, breaking version release. The deprecation cleanup isn't anything you have to be concerned with, but just wanted to clarify.

Right now it looks like you need to implement Read to be able to rely on a ton of infrastructure from std, like Take, Chain, BufReader, BufRead, etc. Would you be re-implementing those in tokio? Or perhaps changing std to allow re-using those without Read?

In general, letting people ever call Read or Write methods on their tokio::net::tcp::TcpStream, or whatever, is clearly wrong. It's a footgun. With that in mind, we don't want to do the same thing again for AsyncSeek.

There's a good chunk of infrastructure that std has and Tokio only has partial answers for, but that is changing as time goes on.

@agrif
Copy link
Author

agrif commented Dec 8, 2018

Ok, I think I've made the required changes. A summary:

  • fa3250f Removed spurious newlines.
  • e1051bb Changed 0.2 to 0.1.5.
  • cfc61a0 I noticed I did not expose AsyncSeek alongside the other traits in the main tokio::io module, so I fixed that.
  • 3468387 Stopped inheriting from Seek, and rewrote implementations to work. For File I used blocking_io(...) which I believe will provide the same behavior that would_block(...) did in the old code.

I removed the implementations for BufReader and BufWriter since I don't think those are useful without the Read/Write traits. However, I did leave in an implementation for Cursor, since that seems to be what the futures preview is doing for AsyncRead etc.

(An aside: should I try to form a similar PR for the futures crate?)

Copy link
Member

@tobz tobz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good to merge.

Thanks again, and sorry for the long lead time on getting it merged. 👍

@ipetkov
Copy link
Member

ipetkov commented Jan 11, 2019

@tobz restarted a failed travis job, CI looks green now, but I'll let you merge

@MarkJr94
Copy link

@tobz is this still good to merge? might need it soon

@kevinkassimo
Copy link
Contributor

Is this PR ready? Similarly in need of this feature.

kevinkassimo added a commit to kevinkassimo/deno that referenced this pull request Feb 16, 2019
This patch contains a special hack that circumvents the current tokio
seek problem.

tokio `seek` is implemented to take ownership of the original File and
emit a new one in its future, which conflicts with the design of
ResourceTable.

To avoid the problem, the current hack makes the FsFile resource
an Option which we could `take` the value ownership out of it. We then
convert the tokio File into a Rust std File, perform the seek, and then
put it back into the resource.

This might be able to drop this hack after
tokio-rs/tokio#785 lands.
Copy link
Member

@carllerche carllerche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question inline

tokio-fs/src/file/mod.rs Show resolved Hide resolved
Copy link
Member

@carllerche carllerche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posting the Request changes review until the question re: poll_seek name conflict is resolved.

ry pushed a commit to denoland/deno that referenced this pull request Feb 18, 2019
This patch contains a special hack that circumvents the current tokio
seek problem.

tokio `seek` is implemented to take ownership of the original File and
emit a new one in its future, which conflicts with the design of
ResourceTable.

To avoid the problem, the current hack makes the FsFile resource
an Option which we could `take` the value ownership out of it. We then
convert the tokio File into a Rust std File, perform the seek, and then
put it back into the resource.

This might be able to drop this hack after
tokio-rs/tokio#785 lands.
@agrif
Copy link
Author

agrif commented Feb 24, 2019

@carllerche I've added a test for File::poll_seek. It looks like it is finding the File implementation even when AsyncSeek is in scope, though I don't see any way to verify this except to enable deprecation warnings on the test and make sure rust emits one. I don't know if you can make a test fail if a deprecation warning is not emitted...

Any suggestions, or is this good?

@carllerche
Copy link
Member

@agrif Ok... I think we can make this work out.

The deprecated annotation should be removed. Unfortunately, because the compiler defaults to the inherent method (on File) there is no easy way for the user to use the trait fn. That is OK though since the trait fn and the inherent fn are the same. The inherent fn can still be marked as #[doc(hidden)].

Besides that, there is a bunch of other smaller tweaks I would do, but I can try to tackle it myself on monday or tuesday if you don't want to (it's a bit hard to explain).

A lot of the tokio-io stuff is in the pattern Tokio used when it was first written. Idioms have changed. You can see tokio-buf for the new idioms.

Basically:

  • tokio-io should re-export SeekFrom.
  • tokio-io should have a util module, in that module there is an AsyncSeekExt trait.
  • The AsyncSeekExt trait has the seek fn (it is not free).

@agrif
Copy link
Author

agrif commented Feb 25, 2019

Ok, I've removed the deprecation annotation on File::poll_seek. As for the other changes, I think I have a rough idea of what you want but am fuzzy on the details. If you are offering to make those, that's probably for the best.

@EliSnow
Copy link

EliSnow commented Apr 9, 2019

Just wanted to ping this to check the status. @carllerche, based on the last comment it looks like @agrif is expecting input from you.

@EliSnow
Copy link

EliSnow commented Apr 9, 2019

@agrif, Can this PR also implement std::io::Seek for tokio::fs::File? I looked over tokio-fs/src/file/mod.rs but I didn't notice it there. Sorry if I missed it.

My use case is I want to be able to use the seek method on a csv Reader build on top of a tokio::fs::File which requires std::io::Seek.

@agrif
Copy link
Author

agrif commented Apr 9, 2019

@EliSnow the original version of this pull request had AsyncSeek inherit Seek, and so tokio::fs::File also implemented Seek. This mirrored how, for example, AsyncRead and Read work. However, it seems like the devs have since cooled on this pattern, see tobz's comment above.

It would be easy enough to add a Seek implementation, but at the moment at least it sounds like that's not something the devs want.

@carllerche
Copy link
Member

Adding AsyncSeek is blocked on #1045. I believe there may be complications w.r.t implementing the proposed trait using the fallback blocking pool. We need to first ensure it can work before introducing the trait.

@ghost
Copy link

ghost commented May 11, 2019

So the concern @carllerche had with poll_seek() in #1045 was the following.

Suppose someone polls a seek operation, maybe several times in succession, and it always returns NotReady. If the seek operation is not driven to completion, is it okay if it is eventually silently completed and the file cursor gets moved?

We considered three different behaviors/guarantees of asynchronous seek:

  1. The first time a seek operation is polled, NotReady is returned, but it begins executing in the background. Successive polls will only return the status of the already started asynchronous seek operation. That means the seek operation will eventually be completed even if not driven to completion by successive polling.

  2. We don't specify whether the asynchronous seek operation will complete or not. If you poll it but don't drive to completion, it may or may not happen in the end.

  3. If NotReady is returned, the seek operation is not completed. Only when Ready is returned the operation is "atomically" completed. So the file cursor is only moved when Ready is returned. In case one polls a seek operation but doesn't drive it to completion, the file will be left intact.

In the end, we chose the 3rd option because that behavior feels most natural and consistent with the way poll_read() and poll_write() move the cursor.

This PR looks good to me and doesn't seem to affect our choice of guarantees around seeking.

@carllerche
Copy link
Member

Unfortunately it looks like there are merge conflicts, but the PR can be merged once those are resolved. Thanks.

@agrif
Copy link
Author

agrif commented May 16, 2019

I've rebased on to the latest master. Along the way, I updated the deprecation annotations to point to tokio-fs 0.1.7, since that will be the next minor point release for that package. If that's not what you folks need, let me know and I can update it.

@agrif
Copy link
Author

agrif commented Jul 23, 2019

Just poking you folks for an update. It's been 235 days since this PR was opened, and I no longer even remember what I was trying to do when I ran into this limitation. Is there something I can do to help this along?

Tzikas pushed a commit to Tzikas/squares that referenced this pull request Sep 10, 2019
This patch contains a special hack that circumvents the current tokio
seek problem.

tokio `seek` is implemented to take ownership of the original File and
emit a new one in its future, which conflicts with the design of
ResourceTable.

To avoid the problem, the current hack makes the FsFile resource
an Option which we could `take` the value ownership out of it. We then
convert the tokio File into a Rust std File, perform the seek, and then
put it back into the resource.

This might be able to drop this hack after
tokio-rs/tokio#785 lands.
@carllerche
Copy link
Member

Follow up here: #1641

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants