10938

Why does Future::select choose the future with a longer sleep period first?

Question:

I'm trying to understand <a href="https://docs.rs/futures/0.1.18/futures/future/trait.Future.html#method.select" rel="nofollow">Future::select</a>: in this example, the future with a longer time delay is returned first.

When I read <a href="https://dev.to/mindflavor/rust-futures-an-uneducated-short-and-hopefully-not-boring-tutorial---part-3---the-reactor-433" rel="nofollow">this article</a> with its example, I get cognitive dissonance. The author writes:

<blockquote>

The select function runs two (or more in case of select_all) futures and returns the first one coming to completion. This is useful for implementing timeouts.

</blockquote>

It seems I don't understand the sense of select.

extern crate futures; extern crate tokio_core; use std::thread; use std::time::Duration; use futures::{Async, Future}; use tokio_core::reactor::Core; struct Timeout { time: u32, } impl Timeout { fn new(period: u32) -> Timeout { Timeout { time: period } } } impl Future for Timeout { type Item = u32; type Error = String; fn poll(&mut self) -> Result<Async<u32>, Self::Error> { thread::sleep(Duration::from_secs(self.time as u64)); println!("Timeout is done with time {}.", self.time); Ok(Async::Ready(self.time)) } } fn main() { let mut reactor = Core::new().unwrap(); let time_out1 = Timeout::new(5); let time_out2 = Timeout::new(1); let task = time_out1.select(time_out2); let mut reactor = Core::new().unwrap(); reactor.run(task); }

I need to process the early future with the smaller time delay, and then work with the future with a longer delay. How can I do it?

Answer1:

If there's one thing to take away from this: <strong><em>never</em></strong> perform blocking or long-running operations inside of asynchronous operations.

If you want a timeout, use something from <a href="https://docs.rs/tokio/0.1.8/tokio/timer/index.html" rel="nofollow">tokio::timer</a>, such as <a href="https://docs.rs/tokio/0.1.8/tokio/timer/struct.Delay.html" rel="nofollow">Delay</a> or <a href="https://docs.rs/tokio/0.1.8/tokio/timer/struct.Timeout.html" rel="nofollow">Timeout</a>:

extern crate futures; // 0.1.23 extern crate tokio; // 0.1.8 use futures::prelude::*; use std::time::{Duration, Instant}; use tokio::timer::Delay; fn main() { let time_out1 = Delay::new(Instant::now() + Duration::from_secs(5)); let time_out2 = Delay::new(Instant::now() + Duration::from_secs(1)); let task = time_out1.select(time_out2); tokio::run(task.map(drop).map_err(drop)); } <hr />

To understand why you get the behavior you do, you have to understand the implementation of futures at a high level.

When you call run, there's a loop that calls poll on the passed-in future. It loops until the future returns success or failure, otherwise the future isn't done yet.

Your implementation of poll "locks up" this loop for 5 seconds because nothing can break the call to sleep. By the time the sleep is done, the future is ready, thus that future is selected.

The implementation of an async timeout conceptually works by checking the clock every time it's polled, saying if enough time has passed or not.

The big difference is that when a future returns that it's not ready, <em>another future</em> can be checked. This is what select does!

A dramatic re-enactment:

<strong>sleep-based timer</strong>

<blockquote>

<strong>core</strong>: Hey select, are you ready to go?

<strong>select</strong>: Hey future1, are you ready to go?

<strong>future1</strong>: Hold on a seconnnnnnnn [... 5 seconds pass ...] nnnnd. Yes!

</blockquote>

<strong>async-based timer</strong>

<blockquote>

<strong>core</strong>: Hey select, are you ready to go?

<strong>select</strong>: Hey future1, are you ready to go?

<strong>future1</strong>: <em>Checks watch</em> No.

<strong>select</strong>: Hey future2, are you ready to go?

<strong>future2</strong>: <em>Checks watch</em> No.

<strong>core</strong>: Hey select, are you ready to go?

[... 1 second passes ...]

<strong>core</strong>: Hey select, are you ready to go?

<strong>select</strong>: Hey future1, are you ready to go?

<strong>future1</strong>: <em>Checks watch</em> No.

<strong>select</strong>: Hey future2, are you ready to go?

<strong>future2</strong>: <em>Checks watch</em> Yes!

</blockquote> <hr />

When you have have an operation that is blocking or long-running, then the appropriate thing to do is to move that work out of the async loop. The most common solution is use a thread pool like <a href="https://docs.rs/futures-cpupool/0.1.8/futures_cpupool/" rel="nofollow">futures-cpupool</a> or <a href="https://docs.rs/tokio-pool/0.1.0/tokio_pool/" rel="nofollow">tokio-pool</a>. However, using either of these to implement a timeout is <em>highly inefficient</em>:

extern crate futures; extern crate futures_cpupool; extern crate tokio_core; use std::thread; use std::time::Duration; use futures::Future; use tokio_core::reactor::Core; use futures_cpupool::CpuPool; fn main() { let pool = CpuPool::new(4); let mut core = Core::new().unwrap(); let time_out1 = pool.spawn_fn::<_, Result<(), ()>>(|| { Ok(thread::sleep(Duration::from_secs(5))) }); let time_out2 = pool.spawn_fn::<_, Result<(), ()>>(|| { Ok(thread::sleep(Duration::from_secs(1))) }); let task = time_out1.select(time_out2); core.run(task).unwrap(); }

Recommend

  • A Method of counting Floating Point Operations in a C++/CUDA Program using PTX
  • Parallel computing of array elements in rust [duplicate]
  • golang, ebpf and functions duration
  • Formatting a string using values from a generic list by LINQ
  • How do I access fields of a *mut libc::FILE?
  • how to sleep accurately in a while loop in C (Linux)?
  • Is it possible to create a macro to implement builder pattern methods?
  • Adding extra filter to polls urls.py causes tests to fail
  • Why do I get incorrect values when implementing HMAC-SHA256?
  • How to prevent a value from being moved?
  • Django AJAX requests during regular request not going through
  • Cannot upload to OneDrive using the new SDK
  • Ajax jQuery multiple calls at the same time - long wait for answer and not able to cancel
  • Can Jackson SerializationFeature be overridden per field or class?
  • Where to put my custom functions in Wordpress?
  • Can I have the cursor start on a particular column by default in jqgrid's edit mode?
  • Hazelcast - OperationTimeoutException
  • Rearranging Cells in UITableView Bug & Saving Changes
  • Numpy divide by zero. Why?
  • AT Commands to Send SMS not working in Windows 8.1
  • php design question - will a Helper help here?
  • Circular dependency while pushing http interceptor
  • Linker errors when using intrinsic function via function pointer
  • File upload with ng-file-upload throwing error
  • Windows forms listbox.selecteditem displaying “System.Data.DataRowView” instead of actual value
  • AngularJs get employee from factory
  • Rails 2: use form_for to build a form covering multiple objects of the same class
  • NSLayoutConstraint that would pin a view to the bottom edge of a superview
  • How can I get HTML syntax highlighting in my editor for CakePHP?
  • Hits per day in Google Big Query
  • FormattedException instead of throw new Exception(string.Format(…)) in .NET
  • How do I configure my settings file to work with unit tests?
  • IndexOutOfRangeException on multidimensional array despite using GetLength check
  • Linking SubReports Without LinkChild/LinkMaster
  • Authorize attributes not working in MVC 4
  • apache spark aggregate function using min value
  • XCode 8, some methods disappeared ? ex: layoutAttributesClass() -> AnyClass
  • Sorting a 2D array using the second column C++
  • Binding checkboxes to object values in AngularJs
  • How to push additional view controllers onto NavigationController but keep the TabBar?