-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Conversation
254f5f6
to
a9430df
Compare
The CI fix was merged to master. Would you be able to rebase? Sorry for the inconvenience. |
a9430df
to
2769af9
Compare
Done |
2769af9
to
f640cc5
Compare
There was a problem hiding this 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 whenrun
, orblock_on
is called. -
SingleThreaded::run
: run the runtime to completion, blocking the current thread and unblocking once all spawned futures have completed executing.
src/runtime/mod.rs
Outdated
mod shutdown; | ||
mod task_executor; | ||
|
||
pub use self::builder::Builder; | ||
pub use self::singlethreaded_runtime::SinglethreadedRuntime; |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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; | ||
|
||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing docs?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/runtime/builder.rs
Outdated
/// # let _ = runtime; | ||
/// # } | ||
/// ``` | ||
pub fn singlethreaded(&mut self) -> io::Result<SinglethreadedRuntime> { |
There was a problem hiding this comment.
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>> >, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 >, |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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) -> () { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
src/runtime/builder.rs
Outdated
@@ -68,6 +68,22 @@ impl Builder { | |||
self | |||
} | |||
|
|||
/// Create a new singlethreaded runtime | |||
/// # Examples |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
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"/ |
@carllerche all your review notes are fixed. @sdroege I am not sure that that's kind of functionality should be implemented for now. |
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 :) |
@carllerche Carl? |
@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. |
There was a problem hiding this 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/singlethreaded.rs
Outdated
// Run the provided future | ||
executor.block_on(f).unwrap(); | ||
// Run all the other futures that are still left in the executor | ||
executor.run().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be removed.
src/runtime/singlethreaded.rs
Outdated
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(); |
There was a problem hiding this comment.
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.
Done |
src/runtime/singlethreaded.rs
Outdated
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
If Also if Also |
src/runtime/singlethreaded.rs
Outdated
|
||
impl Drop for SingleThreaded { | ||
fn drop(&mut self) { | ||
self.shutdown_on_idle(); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this 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 thecurrent_thread::Runtime
.TaskExecutor
- An executor handle forcurrent_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/singlethreaded.rs
Outdated
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(); |
There was a problem hiding this comment.
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.
Splitting the builder sounds good to me. Although not directly related to this pull request, but as a response to:
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. |
@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? |
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? |
@carllerche I did all requested changes except for TaskExecutor |
oops |
* don't copy timer_hanler with reactor_handle
ab33eaf
to
c0e5dfd
Compare
c0e5dfd
to
c5c0383
Compare
Ok, I pushed some tweaks. Let me know if this looks good to y'all. |
It currently has no options.
src/runtime/current_thread/mod.rs
Outdated
//! | ||
//! # Examples | ||
//! | ||
//! Creating a new `Runtime` with default configuration values. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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
There was a problem hiding this 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
Is it intentional that this |
@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 |
Closes #235
Thanks to: #265, #306