Non-tokio executor & timeouts
When using an executor that is different from tokio (in my case glib), launching a request from the client results in the following message at runtime:
050, marker: <Phantomcow> }), cache: ThreadCache { is_unread: true, has_attachment: false, tags: [] } }))
INFO inox_gtk::components::thread_view > load_thread: Thread { thread: Some(Thread { ptr: 0x55a29e2a0050, marker: <Phantomcow> }), cache: ThreadCache { is_unread: true, has_attachment: false, tags: [] } }
thread 'main' panicked at 'there is no timer running, must be called from the context of Tokio runtime', /home/dirk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.2.13/src/time/driver/handle.rs:24:9
stack backtrace:
It seems tokio timeouts are not compatible with other runtimes. It'd be cool if there was a runtime-agnostic solution. Peraps https://github.com/async-rs/futures-timer ?
It is possible to use your own runtime, provided that it is within the context of Tokio.
@vorot93 The problem with the Tokio context is twofold:
- It is either really difficult or impossible to set up the Tokio context inside your custom executor.
- There are no practical examples, documentation, or guides on how to do that.
Speaking as a previous Tokio developer and somebody who has been building the async Rust ecosystem for the past several years, I have no idea how to make a "hello world" example of futures::executor::ThreadPool with the Tokio context set up inside it. It's most likely not possible at all!
I read @carllerche's example you linked to, but how do you substitute // Run the runtime somewhere (background thread?) with ThreadPool?
Whenever the concern of Tokio's incompatibility with other libraries is brought up, the argument is that it's possible to set up the Tokio context inside other runtimes. But until the Tokio documentation shows the most basic "hello world" of how to do that, it is simply not possible to use Tokio with the rest of the async ecosystem.
@vhdirk Possible runtime-agnostic solutions are futures-timer and async-io (my own crate).
Hey, so I spent a few minutes this morning throwing together an example of using tokio with the futures executor ThreadPool. This can be made generic to any executor as long as you wrap the future as shown in the example.
https://gist.github.com/LucioFranco/ec8c6617b7193fd062d1ddc32b5bb991
Most of the complexity here is making it easy to spawn the future with a global runtime. Otherwise, the main thing here is just wrapping the poll call in a handle.enter(|| ...) like carl showed.
Hopefully, this is helpful! We could probably add this as an example to the tokio repo.
@LucioFranco Thank you, this is the first runnable example that I've encountered! Still, I must admit that the code is quite intimidating for a 'hello world'. If I may make a few suggestions on how to improve the situation:
- There should also be an example showing how to set up the context inside
block_on()- the entry point into async code. - Exposing
TokioIointokio-utilsor some crate like that would be helpful. Or at the very least, including its implementation in the docs fortokio::runtime::Runtime. If we're supposed to wrapFuture::poll()for individual futures intoHandle::enter(), why is there onlyHandle::enter()and not something likeHandle::wrap_future()? - It is not always obvious where to get the runtime
Handlefrom. Should I start my own runtime or doHandle::try_current()? Illustrating that in an example would be nice. Callingtry_current()is especially useful when startingThreadPoolfrom an#[tokio::main]. - Rust users struggle with compatibility between
tokio::io::{AsyncRead, AsyncWrite}andfutures::io::{AsyncRead, AsyncWrite}. There is no mention oftokio_util::compatanywhere in https://docs.rs/tokio (you just have to hear about it from someone) and there are zero doc-examples showing how to use it.
If you're looking for the most bang-for-buck, you could just make the following two examples:
-
A
#[tokio::main]that starts aThreadPoolinheriting the tokio runtime. Then, inside theThreadPool, spawn a task that callstokio::time::delay_for(). -
A regular
fn main()that creates a tokio runtime and runsblock_on()that doesfutures::io::copy(tokio::io::stdin(), tokio::io::stdout()). This is a classic "cat" example.
@stjepang this is some great feedback! I will make sure we incorporate a lot of these ideas into tokio 0.3. Will follow up with a proper issue in tokio. Thanks!
This is something tarpc doesn't handle very gracefully today: tokio is an optional feature, and yet tarpc uses tokio timers regardless of whether the tokio feature is enabled. Is it possible to set up just the tokio timer without setting up a full tokio runtime?
@tikue you need something to drive the timer, in tokio we use epoll_timeout/sleep to achieve this. So you need to run the driver anyways. This is the case for every runtime. futures-timer just spawns a background thread and uses a similar implementation.
@stjepang I've opened a bunch of issues in tokio that should cover some of this. Thanks again for the feedback!
@LucioFranco yeah totally, I know there's no getting around that :) but is there a way to set up just the timer's required runtime rather than a full tokio runtime? Sorry if this is a dumb question; I've just kinda ignored the runtime details since #[tokio::main] was introduced :)
@tikue ah yes! If you look at that example I use enable_all you can instead just enable the timer via https://docs.rs/tokio/0.2.22/tokio/runtime/struct.Builder.html#method.enable_time
Nice, thanks!
I've experimented with replacing something like Tokio MPSC into futures and flume, but so far it fails the test by exceeding the deadline. Yet to have the idea why
Anyway, if you are interested checkout https://github.com/stevefan1999-personal/tarpc/tree/patch-no-tokio