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

std: sync: Implement recv_timeout() #33748

Merged
merged 1 commit into from
Jun 23, 2016
Merged

std: sync: Implement recv_timeout() #33748

merged 1 commit into from
Jun 23, 2016

Conversation

emilio
Copy link
Contributor

@emilio emilio commented May 20, 2016

This is an attempt to implement rust-lang/rfcs#962.

I'm not sure about if a change like this would require an rfc or something like
that, and this surely needs a lot more testing, but I wanted to take some eyes
on it before following.

cc @metajack @asajeffrey servo/servo#11279 servo/servo#11283

r? @aturon

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @aturon (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@Aatch
Copy link
Contributor

Aatch commented May 20, 2016

I would assume it requires an RFC before merging, but having an implementation already tends to help the RFC process along.

/cc @rust-lang/libs

///
/// # Examples
///
/// ```
Copy link
Member

Choose a reason for hiding this comment

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

Could this be tagged no_run? Would be unfortunate to have a test that's basically just sleep(1) :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it to 100ms, but let me know if that's not enough so I mark it no-run.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer no-run. Slow tests add up.

@alexcrichton
Copy link
Member

Thanks for the PR @emilio! Unfortunately I think that the implementations for the stream and shared implementations may end up being much more complicated. There's a nontrivial protocol where a receiver registers itself as available to receive data, but if a timeout happens the receiver is not currently "unregistered". The logic here is basically in the decrement method and there's a few things that are set which need to be unset. I haven't though too much about exactly what needs to be done yet, unfortunately. I also think that this could benefit from a much larger number of tests which attempt to exercise a lot of the concurrent aspects of, for example, sending a message right around when a timeout is expiring.

As for the instability of these methods, I think it's fine to land these without an RFC. The new methods clearly follow existing conventions and they're pretty minor additions, so I'd at least think they can go through the normal flesh-out-the-bugs then FCP cycle.

@emilio
Copy link
Contributor Author

emilio commented May 20, 2016

On Fri, May 20, 2016 at 09:42:59AM -0700, Alex Crichton wrote:

Thanks for the PR @emilio! Unfortunately I think that the
implementations for the stream and shared implementations may end up
being much more complicated. There's a nontrivial protocol where a
receiver registers itself as available to receive data, but if a
timeout happens the receiver is not currently "unregistered". The
logic here is basically in the decrement method and there's a few
things that are set which need to be unset. I haven't though too
much about exactly what needs to be done yet, unfortunately. I also
think that this could benefit from a much larger number of tests which
attempt to exercise a lot of the concurrent aspects of, for example,
sending a message right around when a timeout is expiring.

Yeah, totally, I expected it to be way harder than what I did there (my
laptop struggled building rust, and even more to run tests).

Basically I just wanted to make sure there was any intention to get it
landed before keeping working on it :)

As for the instability of these methods, I think it's fine to land
these without an RFC. The new methods clearly follow existing
conventions and they're pretty minor additions, so I'd at least think
they can go through the normal flesh-out-the-bugs then FCP cycle.

Sounds good, thanks!


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#33748 (comment)

@asajeffrey
Copy link

A possible alternative approach would be to provide a receiver later(delay) that produces () after delay, then rx.recv_timeout(delay) is essentially:

   select!(
      x = rx.recv() => Some(x),
      _ = later(delay) => None,
   )

All the deregistration logic is already in select, which might simplify matters.

This is essentially half of Concurrent ML (cc @larsbergstrom), the bit that's missing is the ability to add custom handlers to receivers that are executed on deregistration.

@emilio
Copy link
Contributor Author

emilio commented May 20, 2016

IIRC this was the approach that got backed out (old_io::Timer maybe?).

On Fri, May 20, 2016 at 10:36:58AM -0700, Alan Jeffrey wrote:

A possible alternative approach would be to provide a receiver
later(delay) that produces () after delay, then
rx.recv_timeout(delay) is essentially:

   select!(
      x = rx.recv() => Some(x),
      _ = later(delay) => None,
   )

All the deregistration logic is already in select, which might
simplify matters.

This is essentially half of Concurrent ML (cc @larsbergstrom), the bit
that's missing is the ability to add custom handlers to receivers that
are executed on deregistration.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#33748 (comment)

@emilio
Copy link
Contributor Author

emilio commented May 21, 2016

Ok, I did more work on this today, and I think I got all the invariants right.

r? @alexcrichton

@rust-highfive rust-highfive assigned alexcrichton and unassigned aturon May 21, 2016
@@ -216,7 +216,7 @@ impl<T> Packet<T> {
Ok(())
}

pub fn recv(&mut self) -> Result<T, Failure> {
pub fn recv_impl(&mut self, deadline: Option<Instant>) -> Result<T, Failure> {
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps this could just be the only exported function of each of these modules? e.g.:

pub fn recv(&mut self, deadline: Option<Instant>) -> ...

@alexcrichton
Copy link
Member

Slick idea using abort_selection @emilio! I totally forgot that function existed...

I think that the logic here all looks sound to me given that we're using that, so just a few minor nits here and there and otherwise looks great to me.

@alexcrichton
Copy link
Member

And since this is likely close to landing now, cc @rust-lang/libs. Any thoughts on landing this unstable vs requiring an RFC?

self.recv_max_until(Instant::now() + timeout)
}

fn recv_max_until(&self, deadline: Instant) -> Result<T, RecvTimeoutError> {

Choose a reason for hiding this comment

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

At some point we might want to make this part of the public API, to avoid users jumping back and forth between Instant and Duration.

@asajeffrey
Copy link

Yay, this looks like it'll land!

@alexcrichton
Copy link
Member

Hm thinking some more, I wonder if to be performant we want to avoid the usage of Instant::now? In the fast path where you don't block it may be advantageous to avoid calling that. Could you whip up a quick benchmark to see the overhead of recv() on a full channel vs recv_timeout(Duration::new(1, 0)) on a full channel?

@alexcrichton alexcrichton added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label May 24, 2016
@emilio
Copy link
Contributor Author

emilio commented May 25, 2016 via email

@asajeffrey
Copy link

My half-baked opinions about relative-time timeouts (parameterized by a Duration) vs absolute-time timeouts (parameterized by an Instant)...

The API that end-users usually want is relative-time, e.g. "download this file, but give up after ten minutes", which is why APIs tend to be relative-time.

When you're implementing a relative-time API, you quite often want to do it using an absolute-time API, since you often have to use a while loop:

   fn blah_timeout(&self, timeout: Duration) {
      self.blah_until(Instant::now() + timeout);
   }
   fn blah_until(&self, deadline: Instant) {
      loop {
          let done = self.lower_level_thing_until(deadline);
          if done { break; }
          ...
      }
   }

There's a lot less jumping back and forth between Instant and Duration if you provide both APIs, which helps with @alexcrichton's observation about performance, and also helps get rid of errors.

Annoyingly, thread.park_timeout only provides the relative-time API, not the absolute-time one, but if it provided both then channel.recv could also provide both.

@alexcrichton
Copy link
Member

Yeah relative vs absolute is something we haven't explored much in libstd just yet (mainly b/c Instant was only recently stabilized). Absolute times are also tricky with things like clock drift, but for now we probably want to stick to the convention in the rest of libstd of just taking a Duration.

@emilio yeah it's true that there'd be some timing difference to attempt a receive first, but in general this shouldn't be used for precise timing and just "don't block for too long", in which either case should suffice

@emilio
Copy link
Contributor Author

emilio commented May 26, 2016

@alexcrichton So here are the results for my benchmark, let me know if you can think of another test or anything that could be valuable:

     Running target/release/recv_timeout_bench-b11e3ab850520235

running 2 tests
test recv_full         ... bench:          48 ns/iter (+/- 10)
test recv_timeout_full ... bench:          74 ns/iter (+/- 24)

test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured

So yeah, definitely there's some overhead. I don't think there's too much, but if you want me to add an optimistic try_recv, I'll just do it :)

@alexcrichton
Copy link
Member

Ah yeah that'd do it! With a fast-path recv being almost 2x slower, perhaps we can try with a try_recv up front?

@emilio
Copy link
Contributor Author

emilio commented May 27, 2016

@alexcrichton: done and rebased! :-)

#[test]
fn recv_timeout_upgrade() {
let (tx, rx) = channel::<()>();
let timeout = Duration::from_millis(1000);
Copy link
Member

Choose a reason for hiding this comment

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

Could these be more like 1ms to prevent tests from blocking?

#[test]
fn stress_recv_timeout_two_threads() {
let (tx, rx) = channel();
let stress = stress_factor();
Copy link
Member

Choose a reason for hiding this comment

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

This stress_factor() is actually almost always 1 nowadays, so perhaps a more explicit count could be used?

@alexcrichton
Copy link
Member

Ok, r=me modulo a few minor nits (you may also want to squash some commits).

I'll leave this tagged with T-libs to ensure this comes up during triage, however, to ensure we get a chance to discuss the API here. Implementation wise everything looks good to me, thanks again @emilio!

@emilio
Copy link
Contributor Author

emilio commented Jun 1, 2016

Ok, done! I left a 10ms timeout in one of the test IIRC, because without it the test would be absolutely pointless. I can re-edit it though.

Should I open an issue to fill-in the issue number instead of 0000?

Thanks for the reviews @alexcrichton! :)

@alexcrichton
Copy link
Member

Oh right yeah thanks for the reminder, I've opened #34029 to suffice for this.

@emilio
Copy link
Contributor Author

emilio commented Jun 2, 2016

Issue number updated :)

@alexcrichton
Copy link
Member

The libs team discussed this PR during triage today and the decision was to merge, thanks again for your patience @emilio!

@bors: r+

@bors
Copy link
Contributor

bors commented Jun 21, 2016

📌 Commit e95f9c9 has been approved by alexcrichton

@bors
Copy link
Contributor

bors commented Jun 21, 2016

⌛ Testing commit e95f9c9 with merge 908e402...

@bors
Copy link
Contributor

bors commented Jun 21, 2016

💔 Test failed - auto-win-gnu-32-opt-rustbuild

@emilio
Copy link
Contributor Author

emilio commented Jun 22, 2016

Heh, missed the type param in the docs example, just changed mpsc::channel() for mpsc::channel::<()>().

re-r? @alexcrichton

@alexcrichton
Copy link
Member

Looks like the travis error may be legit:


failures:

---- sync::mpsc::Receiver<T>::recv_timeout_0 stdout ----
    <anon>:4:33: 4:49 error: use of unstable library feature 'mpsc_recv_timeout' (see issue #34029)
<anon>:4     use std::sync::mpsc::{self, RecvTimeoutError};
                                         ^~~~~~~~~~~~~~~~
<anon>:4:33: 4:49 help: add #![feature(mpsc_recv_timeout)] to the crate attributes to enable
<anon>:10:16: 10:41 error: use of unstable library feature 'mpsc_recv_timeout' (see issue #34029)
<anon>:10 assert_eq!(Err(RecvTimeoutError::Timeout), recv.recv_timeout(timeout));
                         ^~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:10:1: 10:72 note: in this expansion of assert_eq! (defined in <std macros>)
<anon>:10:16: 10:41 help: add #![feature(mpsc_recv_timeout)] to the crate attributes to enable
<anon>:10:49: 10:61 error: use of unstable library feature 'mpsc_recv_timeout' (see issue #34029)
<anon>:10 assert_eq!(Err(RecvTimeoutError::Timeout), recv.recv_timeout(timeout));
                                                          ^~~~~~~~~~~~
<anon>:10:1: 10:72 note: in this expansion of assert_eq! (defined in <std macros>)
<anon>:10:49: 10:61 help: add #![feature(mpsc_recv_timeout)] to the crate attributes to enable
error: aborting due to previous error(s)
thread 'sync::mpsc::Receiver<T>::recv_timeout_0' panicked at 'Box<Any>', src/librustc/session/mod.rs:171
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    sync::mpsc::Receiver<T>::recv_timeout_0

@emilio
Copy link
Contributor Author

emilio commented Jun 22, 2016

Yup, sorry, first rust patch and I don't know of any make target a la
check-sage1-std NO_REBUILD=1 that allow me to skip rebuilding the
world after a doc change.

Should be fixed.

On Wed, Jun 22, 2016 at 10:55:55AM -0700, Alex Crichton wrote:

Looks like the travis error may be legit:


failures:

---- sync::mpsc::Receiver<T>::recv_timeout_0 stdout ----
  <anon>:4:33: 4:49 error: use of unstable library feature 'mpsc_recv_timeout' (see issue #34029)
<anon>:4     use std::sync::mpsc::{self, RecvTimeoutError};
                                         ^~~~~~~~~~~~~~~~
<anon>:4:33: 4:49 help: add #![feature(mpsc_recv_timeout)] to the crate attributes to enable
<anon>:10:16: 10:41 error: use of unstable library feature 'mpsc_recv_timeout' (see issue #34029)
<anon>:10 assert_eq!(Err(RecvTimeoutError::Timeout), recv.recv_timeout(timeout));
                         ^~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:10:1: 10:72 note: in this expansion of assert_eq! (defined in <std macros>)
<anon>:10:16: 10:41 help: add #![feature(mpsc_recv_timeout)] to the crate attributes to enable
<anon>:10:49: 10:61 error: use of unstable library feature 'mpsc_recv_timeout' (see issue #34029)
<anon>:10 assert_eq!(Err(RecvTimeoutError::Timeout), recv.recv_timeout(timeout));
                                                          ^~~~~~~~~~~~
<anon>:10:1: 10:72 note: in this expansion of assert_eq! (defined in <std macros>)
<anon>:10:49: 10:61 help: add #![feature(mpsc_recv_timeout)] to the crate attributes to enable
error: aborting due to previous error(s)
thread 'sync::mpsc::Receiver<T>::recv_timeout_0' panicked at 'Box<Any>', src/librustc/session/mod.rs:171
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    sync::mpsc::Receiver<T>::recv_timeout_0

You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#33748 (comment)

@alexcrichton
Copy link
Member

@bors: r+

@bors
Copy link
Contributor

bors commented Jun 22, 2016

📌 Commit b94b158 has been approved by alexcrichton

@bors
Copy link
Contributor

bors commented Jun 22, 2016

⌛ Testing commit b94b158 with merge 6dcc2c1...

bors added a commit that referenced this pull request Jun 22, 2016
std: sync: Implement recv_timeout()

This is an attempt to implement rust-lang/rfcs#962.

I'm not sure about if a change like this would require an rfc or something like
that, and this surely needs a lot more testing, but I wanted to take some eyes
on it before following.

cc @metajack @asajeffrey servo/servo#11279 servo/servo#11283

r? @aturon
@bors bors merged commit b94b158 into rust-lang:master Jun 23, 2016
@emilio emilio deleted the mpsc-recv-timeout branch June 23, 2016 00:34
@emilio
Copy link
Contributor Author

emilio commented Jun 23, 2016

Finally! Thanks for all your patience @alexcrichton :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants