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

Singlethreaded runtime #308

Merged
merged 16 commits into from
May 2, 2018
Merged

Conversation

kpp
Copy link
Contributor

@kpp kpp commented Apr 8, 2018

Closes #235

Thanks to: #265, #306

@carllerche
Copy link
Member

The CI fix was merged to master. Would you be able to rebase? Sorry for the inconvenience.

@kpp
Copy link
Contributor Author

kpp commented Apr 9, 2018

Done

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.

Thanks for doing this! This is a great start.

I left some comments inline. On top of that, would it be possible to add some additional fns to match current_thread some.

  • SingleThreaded::spawn: spawn the future onto the runtime. This does not start running it immediately, but it runs it when run, or block_on is called.

  • SingleThreaded::run: run the runtime to completion, blocking the current thread and unblocking once all spawned futures have completed executing.

mod shutdown;
mod task_executor;

pub use self::builder::Builder;
pub use self::singlethreaded_runtime::SinglethreadedRuntime;
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 named singlethreaded (and the file singlethreaded.rs). It already is in a runtime module :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

use std::cell::RefCell;
use std::io;

///
Copy link
Member

Choose a reason for hiding this comment

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

Missing docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I don't know what to write here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

/// # let _ = runtime;
/// # }
/// ```
pub fn singlethreaded(&mut self) -> io::Result<SinglethreadedRuntime> {
Copy link
Member

Choose a reason for hiding this comment

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

This should probably be named single_threaded and the type SingleThreaded (since it is two words).

I would probably also recommend dropping Runtime from SingleThreaded since it is scoped in the runtime module already.

pub struct SinglethreadedRuntime {
reactor_handle: tokio_reactor::Handle,
timer_handle: timer::Handle,
executor: RefCell< CurrentThread<Timer<Reactor>> >,
Copy link
Member

Choose a reason for hiding this comment

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

Why is the RefCell needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

reactor_handle: tokio_reactor::Handle,
timer_handle: timer::Handle,
executor: RefCell< CurrentThread<Timer<Reactor>> >,
enter: RefCell< Enter >,
Copy link
Member

Choose a reason for hiding this comment

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

Enter should not be stored on the struct. Instead, it should be created "on demand" when a function needs to "enter" the executor.

Here is how tokio-core does it: https://github.com/tokio-rs/tokio-core/blob/master/src/reactor/mod.rs#L224-L241.

// Run the provided future
executor.block_on(f).unwrap();
// Run all the other futures that are still left in the executor
executor.run().unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

I would follow the current_thread behavior here and return immediately once the executor.block_on call returns.

So, that would mean removing executor.run() after calling executor.block_on.

tokio_executor::with_default(&mut default_executor, enter, |enter| {
let mut executor = executor.enter(enter);
// Run the provided future
executor.block_on(f).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

Could you remove the unwrap and return the result of executor.block_on to the caller?

/// Blocks and runs the given future.
///
/// This is similar to running a runtime, but uses only the current thread.
pub fn block_on<F: Future<Item = (), Error = ()>>(&self, f: F) -> () {
Copy link
Member

Choose a reason for hiding this comment

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

This can probably be &mut self.

let reactor_handle = self.reactor_handle.clone();
let timer_handle = self.timer_handle.clone();
let mut executor = self.executor.borrow_mut();
let mut enter = self.enter.borrow_mut();
Copy link
Member

Choose a reason for hiding this comment

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

The enter instance would be created here.

@@ -68,6 +68,22 @@ impl Builder {
self
}

/// Create a new singlethreaded runtime
/// # Examples
Copy link
Member

Choose a reason for hiding this comment

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

Extra line needed before # Examples to make rustdoc happy (i think).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@sdroege
Copy link
Contributor

sdroege commented Apr 11, 2018

This looks like a good start, thanks :) It's also more or less what I'm doing in my code right now. However it would currently be impossible to spawn futures from another thread on this runtime as it can't be shared with other threads.

A solution to that would be to provide some kind of mpsc channel (obviously not directly but wrapped in some kind of "Spawner"/Handle struct for this runtime) from the runtime that can be used for other threads to spawn futures, by passing those futures over the channel and then spawning them locally from the runtime thread directly.

@kpp
Copy link
Contributor Author

kpp commented Apr 11, 2018

@carllerche all your review notes are fixed.

@sdroege I am not sure that that's kind of functionality should be implemented for now.

@sdroege
Copy link
Contributor

sdroege commented Apr 11, 2018

For my use case I would need that and it seems quite easy to implement. I mean, in my implementation of the same kind of runtime I did exactly that :)

@kpp
Copy link
Contributor Author

kpp commented Apr 11, 2018

@carllerche Carl?

@sdroege
Copy link
Contributor

sdroege commented Apr 11, 2018

@kpp If you don't implement it, I'll send a PR for that once yours here is merged :)

@kpp
Copy link
Contributor Author

kpp commented Apr 12, 2018

@kpp If you don't implement it, I'll send a PR for that once yours here is merged :)

Good news! I don't actually get what must be implement it so I would be grateful for you if you implemented it.

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.

Thanks for the updates. This looks much better.

I added some inline comments as well as a bikeshedding level question that I am unsure about.

Also, would it be possible to add a test? It could basically just be a copy of this updated for the single threaded runtime.

src/runtime/builder.rs Outdated Show resolved Hide resolved
src/runtime/singlethreaded.rs Outdated Show resolved Hide resolved
src/runtime/singlethreaded.rs Outdated Show resolved Hide resolved
// Run the provided future
executor.block_on(f).unwrap();
// Run all the other futures that are still left in the executor
executor.run().unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

This would be removed.

tokio_executor::with_default(&mut default_executor, enter, |enter| {
let mut executor = self.executor.enter(enter);
// Run the provided future
executor.block_on(f).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

The unwrap isn't needed. Just return the ret value out of this function.

@kpp
Copy link
Contributor Author

kpp commented Apr 14, 2018

Also, would it be possible to add a test? It could basically just be a copy of this updated for the single threaded runtime.

Done

tokio_executor::with_default(&mut default_executor, enter, |enter| {
let mut executor = executor.enter(enter);
// Run all the other futures that are still left in the executor
executor.run().unwrap();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sdroege should it be done like this?

Copy link
Member

Choose a reason for hiding this comment

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

The unwrap should not be needed once the return type of the function is changed.

@stbuehler
Copy link
Contributor

If CurrentThread would forward the inner park errors, some of those unwrap calls could return the (io) errors instead. This might change the return types of some functions, so perhaps think about this before merging?

Also if CurrentThread (and maybe Entered too) would provide get_park/get_park_mut (as Timer does) the handles wouldn't need to be stored.

Also Timer doesn't park if it reached a deadline (afaict), which means it stalls any IO handling (it should park with a duration of 0 seconds). CurrentThread doesn't have this problem, apart from turn it always parks afaics.


impl Drop for SingleThreaded {
fn drop(&mut self) {
self.shutdown_on_idle();
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems suboptimal as it would always block when dropping the runtime if there are still futures pending, which can be quite unexpected

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is how it is done for multi-threaded runtime

Copy link
Contributor

Choose a reason for hiding this comment

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

The multi-threaded runtime uses shutdown_now, which immediately cancels any pending futures. That's equivalent, in the single threaded case, of just dropping the executor/reactor/timer

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.

Sorry for the response delay. I have been without internet connectivity for the past week.

After discussing this with a bunch of people, I have another module layout proposal.

Instead of putting all the types in tokio::runtime, I suggest that a new module is added: tokio::runtime::current_thread. Then, this would contain the following types:

  • Runtime - The current thread runtime.
  • Builder - Builds the current_thread::Runtime.
  • TaskExecutor - An executor handle for current_thread::Runtime (though this can be added in a later PR).

The reasoning behind this change is that:

  • The thread pool version of Runtime is intended to be the primary and default.
  • SingleThread does not really reflect the main reason for this separate type, which is that all the logic happens on the current thread. A threadpool based runtime can be started with 1 thread.
  • The builder options will be significantly different between the current thread runtime and the threadpool runtime.
  • Since there are multiple types that are "duplicated", it makes sense to split them up into another module.

Does anyone else have thoughts on this API?

src/runtime/builder.rs Outdated Show resolved Hide resolved
src/runtime/mod.rs Outdated Show resolved Hide resolved
src/runtime/mod.rs Outdated Show resolved Hide resolved
src/runtime/singlethreaded.rs Outdated Show resolved Hide resolved
src/runtime/singlethreaded.rs Outdated Show resolved Hide resolved
src/runtime/singlethreaded.rs Outdated Show resolved Hide resolved
src/runtime/singlethreaded.rs Outdated Show resolved Hide resolved
src/runtime/singlethreaded.rs Outdated Show resolved Hide resolved
tokio_executor::with_default(&mut default_executor, enter, |enter| {
let mut executor = executor.enter(enter);
// Run all the other futures that are still left in the executor
executor.run().unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

The unwrap should not be needed once the return type of the function is changed.

src/runtime/singlethreaded.rs Outdated Show resolved Hide resolved
@stbuehler
Copy link
Contributor

Splitting the builder sounds good to me. Although not directly related to this pull request, but as a response to:

  • The thread pool version of Runtime is intended to be the primary and default.

I think this is the wrong choice in the long term; imho the single threaded executor should become the default. A thread-pool executor is going to hide lots of bad code (running long-running cpu-heavy calculations, blocking dist/database IO, ... in the polled futures); as long as a developer has lots of cores he will never see those issues. And almost no one will actually need the event handling to run on multiple cores to handle the "event handling" load; and if you do, you probably are better off manually distributing the load across multiple single-threaded event loops to avoid all those context switches. (I.e. my main issue with the pool is the default number of threads being set to the number of cores.)

Not sure if this PR already adds support for this, but it would be nice to run a single threaded runtime as default executor (and reactor+timer) for all threads.

@sdroege
Copy link
Contributor

sdroege commented Apr 24, 2018

Instead of putting all the types in tokio::runtime, I suggest that a new module is added: tokio::runtime::current_thread

@carllerche seems fine with me, but you would still be fine to have some kind of handle for that runtime which can be sent to other threads for scheduling futures on this runtime?

@sdroege
Copy link
Contributor

sdroege commented Apr 24, 2018

I think this is the wrong choice in the long term; imho the single threaded executor should become the default.

I would agree with this. Also the multi-threaded version has non-trivial performance penalty (due to context switching, 2-3x slower in one of my cases) and I'm not aware of a workload where it is actually faster (does someone?), apart from the ones that @stbuehler mentions: doing CPU intense work on the runtime directly instead of offloading to a thread-pool.

Is the recommended usage of the runtime going to be to directly do CPU intense work there instead of using a separate thread pool?

@kpp
Copy link
Contributor Author

kpp commented Apr 24, 2018

@carllerche I did all requested changes except for TaskExecutor

@kpp kpp closed this Apr 24, 2018
@kpp
Copy link
Contributor Author

kpp commented Apr 24, 2018

oops

@kpp kpp reopened this Apr 24, 2018
@carllerche
Copy link
Member

Ok, I pushed some tweaks. Let me know if this looks good to y'all.

It currently has no options.
//!
//! # Examples
//!
//! Creating a new `Runtime` with default configuration values.
Copy link
Contributor

Choose a reason for hiding this comment

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

... and running a future f until its completion and returning its result

Copy link
Contributor

Choose a reason for hiding this comment

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

Also there currently is no configuration at all, so saying default configuration is a bit useless :)

Copy link
Contributor

Choose a reason for hiding this comment

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

These docs are also duplicated further down

Copy link
Contributor

@sdroege sdroege left a comment

Choose a reason for hiding this comment

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

Looks all good to me except for the docs

@carllerche carllerche merged commit 2465483 into tokio-rs:master May 2, 2018
@kpp kpp deleted the singlethreaded_runtime branch May 2, 2018 19:00
@main--
Copy link
Contributor

main-- commented May 8, 2018

Is it intentional that this Runtime does not expose a reactor Handle? After some digging I finally figured out that I'm supposed to use Handle::current() inside a future::lazy but coming from tokio-core 0.1.9 (when I last used tokio) I miss the simplicity and straightforwardness of being guided by types and documentation instead of having to worry about being in the right thread local context.

@carllerche
Copy link
Member

@main-- what do you need the handle for?

It is kind of intended that it is omitted. The default runtime exposes it, but I think it was a mistake. So, I'm taking more care here. Could you open a new issue explaining why you need to call Handle::current()?

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.

None yet

7 participants