Skip to content

Refactor Sink trait #765

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

Merged
merged 4 commits into from
Feb 17, 2018
Merged

Refactor Sink trait #765

merged 4 commits into from
Feb 17, 2018

Conversation

cramertj
Copy link
Member

Fix #751

Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

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

Looks good to me, thanks! I think some tests are failing though?

I'm a little fuzzy on what the protocol is in terms of how to use a sink, but we can always flesh that out later.

/// It will contain a value of type `T` if one was passed to `start_send`
/// after the channel was closed.
#[derive(Debug)]
pub struct ChannelClosed<T>(Option<T>);
Copy link
Member

Choose a reason for hiding this comment

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

How come this was needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Previously, it was always the case that an error contained a T. That's not the case anymore, since poll_ready doesn't have a T to respond with (previously, the only function that could error was start_send, which has a T).

Copy link
Member

Choose a reason for hiding this comment

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

Hm could we mirror the Option in the original futures-channel crate as well though? (to avoid the introduction of another type)

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure-- i debated about this, since it's sort of misleading to the end user, because some functions will only ever return Some or None. In the Sink trait that's not preventable, but it is if you're directly using functions on Channel. I think I agree that the extra complexity isn't worth it, though.

Copy link
Member

Choose a reason for hiding this comment

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

Eh I'd probably be fine either way, I understand now what's going on at least :)

Copy link
Member

Choose a reason for hiding this comment

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

I guess the key question to me is whether we can expect start_send to always be able to give you the value back on an error, in which case the Sink trait should probably continue to use a wrapper around the error type.

cc @carllerche

@cramertj
Copy link
Member Author

@alexcrichton I've pushed another commit fixing up futures-util.

}

fn close(&mut self, _: &mut task::Context) -> Poll<(), SendError<T>> {
fn start_close(&mut self) -> Result<(), Self::SinkError> {
Ok(())
Copy link
Contributor

Choose a reason for hiding this comment

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

This has confused me in the past. I would expect to be able to try to close a channel from the sender side, even if I have multiple senders. Should this instead try to close the channel, instead of being a noop?

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you open an issue for this? I'd prefer to keep this PR scoped to just refactoring changes, not behavioral ones.

type SinkError = ChannelClosed<T>;

fn poll_ready(&mut self, _: &mut task::Context) -> Poll<(), Self::SinkError> {
Ok(Async::Ready(()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this also check the inherent poll_ready? An unbounded channel can still be closed.

Copy link
Member Author

Choose a reason for hiding this comment

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

If it's closed, it will throw an error in start_send. However, I think I agree with you that it would be good to allow the client to stop producing values in these cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

(note that to accomplish this, i'll be changing the inherent poll_ready from &mut self to &self)

Copy link
Member Author

Choose a reason for hiding this comment

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

oh, it seems I can't actually do that, because poll_ready calls poll_unparked internally, which mutates self.maybe_parked.

Copy link
Contributor

Choose a reason for hiding this comment

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

As a user, I find it surprising that passing tx would let poll_ready tell me it was closed, but &tx wouldn't.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't disagree. Can you open an issue for this?

Copy link
Member

Choose a reason for hiding this comment

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

So just to confirm, we're gonna tackle this in a follow-up PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I think that's a good idea.

@cramertj cramertj requested a review from aturon February 16, 2018 20:04
Copy link
Member

@aturon aturon left a comment

Choose a reason for hiding this comment

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

LGTM, left a couple of questions but nothing big.

/// It will contain a value of type `T` if one was passed to `start_send`
/// after the channel was closed.
#[derive(Debug)]
pub struct ChannelClosed<T>(Option<T>);
Copy link
Member

Choose a reason for hiding this comment

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

I guess the key question to me is whether we can expect start_send to always be able to give you the value back on an error, in which case the Sink trait should probably continue to use a wrapper around the error type.

cc @carllerche

type SinkError = ChannelClosed<T>;

fn poll_ready(&mut self, _: &mut task::Context) -> Poll<(), Self::SinkError> {
Ok(Async::Ready(()))
Copy link
Member

Choose a reason for hiding this comment

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

So just to confirm, we're gonna tackle this in a follow-up PR?


/// The result of an asynchronous attempt to send a value to a sink.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AsyncSink<T> {
Copy link
Member

Choose a reason for hiding this comment

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

ahhhhhh so nice

Copy link
Member Author

Choose a reason for hiding this comment

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

\o/

@cramertj
Copy link
Member Author

@aturon

I guess the key question to me is whether we can expect start_send to always be able to give you the value back on an error, in which case the Sink trait should probably continue to use a wrapper around the error type.

@carllerche said in #751:

IMO the current start_send API is pretty unusable in practice. The primary reason is that it is impossible to return the send item in all cases that are non trivial. The poll_ready strategy has been what I've been using and has been working quite well in practice.

@aturon
Copy link
Member

aturon commented Feb 17, 2018

@cramertj ok, that's good enough for me :-)

@aturon aturon merged commit 3e5aa4b into rust-lang:0.2 Feb 17, 2018
@aturon
Copy link
Member

aturon commented Feb 17, 2018

Merged!

@cramertj cramertj deleted the refactor-sink branch February 17, 2018 00:12
@carllerche
Copy link
Member

@aturon @cramertj

yes, it should definitely not be assumed that, once a value is sent, it can always be returned on error. poll_ready lets you have a pretty good idea that the value is accepted, however there is is always the possibility of a race. For example, poll_ready() returns Ready, the channel closes, then start_send is called. In this case, start_send will error.

Now, some Sink implementations are able to always guarantee that the value can be returned on error. In those cases, the implementations are free to include the send value.

enum SendError<T> {
    Closed(Option<T>), // Must be option to handle returning this on `poll_ready`.
    // other variants
}

impl<T> Sink for Sender<T> {
    type Item = T;
    type Error = SendError<T>;
}

This is probably roughly what @cramertj did (but I didn't look in detail).

The implementation can document that T will always be returned when the error is generated from start_send, but the main point is that the decision is left up to the implementation.

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.

5 participants