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

Replace std::task with std::thread, remove librustrt, make final "runtime" features lazily initialize #19654

Merged
merged 24 commits into from
Dec 19, 2014

Conversation

aturon
Copy link
Member

@aturon aturon commented Dec 9, 2014

This PR substantially narrows the notion of a "runtime" in Rust, and allows calling into Rust code directly without any setup or teardown.

After this PR, the basic "runtime support" in Rust will consist of:

  • Unwinding and backtrace support
  • Stack guards

Other support, such as helper threads for timers or the notion of a "current thread" are initialized automatically upon first use.

When using Rust in an embedded context, it should now be possible to call a Rust function directly as a C function with absolutely no setup, though in that case panics will cause the process to abort. In this regard, the C/Rust interface will look much like the C/C++ interface.

In more detail, this PR:

  • Merges librustrt back into std::rt, undoing the facade. While doing so, it removes a substantial amount of redundant functionality (such as mutexes defined in the rt module). Code using librustrt can now call into std::rt to e.g. start executing Rust code with unwinding support.
  • Allows all runtime data to be initialized lazily, including the "current thread", the "at_exit" infrastructure, and the "args" storage.
  • Deprecates and largely removes std::task along with the widespread requirement that there be a "current task" for many APIs in std. The entire task infrastructure is replaced with std::thread, which provides a more standard API for manipulating and creating native OS threads. In particular, it's possible to join on a created thread, and to get a handle to the currently-running thread. In addition, threads are equipped with some basic blocking support in the form of park/unpark operations (following a tradition in some OSes as well as the JVM). See the std::thread documentation for more details.
  • Channels are refactored to use a new internal blocking infrastructure that itself sits on top of park/unpark.

One important change here is that a Rust program ends when its main thread does, following most threading models. On the other hand, threads will often be created with an RAII-style join handle that will re-institute blocking semantics naturally (and with finer control).

This is very much a:

[breaking-change]

Closes #18000
r? @alexcrichton

@aturon
Copy link
Member Author

aturon commented Dec 9, 2014

Note: this PR does not attempt to exhaustively replace the task terminology with thread, nor does it clear out all use of the now-deprecated APIs. Both are left to follow-up work.

@aturon
Copy link
Member Author

aturon commented Dec 9, 2014

Note also: the implementation of park/unpark is currently pretty silly: it just uses a lock and condvar. In the future these can be implemented in more clever ways, e.g. following the JVM.

@eddyb
Copy link
Member

eddyb commented Dec 9, 2014

cc @thestinger

@@ -92,7 +92,7 @@ impl<'a, T, Sized? B> BorrowFrom<Cow<'a, T, B>> for B where B: ToOwned<T> {

/// Trait for moving into a `Cow`
pub trait IntoCow<'a, T, Sized? B> {
/// Moves `self` into `Cow`
/// Moves `serlf` into `Cow`
Copy link
Member

Choose a reason for hiding this comment

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

serlf

@aturon
Copy link
Member Author

aturon commented Dec 9, 2014

Once piece of feedback I'd like: is spawn and with_join the right setup? That makes detached threads the "default". We could alternatively do spawn and spawn_detached, but I worry this will be surprising to people.

no_send: NoSend,
}

pub fn tokens() -> (WaitToken, SignalToken) {
Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity, doesn't this abstraction break if the thread is blocked on anything other than WaitToken while some SignalTokens are signaled? I'm thinking that you call tokens, signal the token, manually call Thread::park(), and then call wait, you'd sleep forever, right? That seems pretty silly though, and I imagine we could document this example when it's a public-facing API.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's correct that you won't get unparked by that signal, but you could be unparked by a different signal. But in any case, you're probably doing something wrong.

(Note that the intent of structures like this and park/unpark is for building blocking concurrency abstractions like channels; they aren't a good fit for application-level concurrent programming. See http://gee.cs.oswego.edu/dl/papers/aqs.pdf)

aturon and others added 16 commits December 18, 2014 23:31
There's always a fun time having two sets of standard libraries when testing!
We need to be sure to init thread_info before we init args for example because
args is grabbing locks which may entail looking at the local thread eventually.
This commit is part of a series that introduces a `std::thread` API to
replace `std::task`.

In the new API, `spawn` returns a `JoinGuard`, which by default will
join the spawned thread when dropped. It can also be used to join
explicitly at any time, returning the thread's result. Alternatively,
the spawned thread can be explicitly detached (so no join takes place).

As part of this change, Rust processes now terminate when the main
thread exits, even if other detached threads are still running, moving
Rust closer to standard threading models. This new behavior may break code
that was relying on the previously implicit join-all.

In addition to the above, the new thread API also offers some built-in
support for building blocking abstractions in user space; see the module
doc for details.

Closes rust-lang#18000

[breaking-change]
The current implementations use `std::sync` primitives, but these primitives
currently end up relying on `thread_info` and a local `Thread` being available
(mainly for checking the panicking flag).

To get around this, this commit lowers the abstractions used by the windows
thread_local implementation as well as the at_exit_imp module. Both of these
modules now use a `sys::Mutex` and a `static mut` and manage the
allocation/locking manually.
This flag is somewhat tied to the `unwind` module rather than the `thread_info`
module, so this commit moves it into that module as well as allowing the same OS
thread to call `unwind::try` multiple times. Previously once a thread panicked
its panic flag was never reset, even after exiting the panic handler.
... and address other rebasing fallout.
The [final step](rust-lang#19654) of
runtime removal changes the threading/process model so that the process
shuts down when the main thread exits. But several shared resources,
like the helper thread for timeouts, are shut down when the main thread
exits (but before the process ends), and they are not prepared to be
used after shut down, but other threads may try to access them during
the shutdown sequence of the main thread.

As an interim solution, the `at_exit` cleanup routine is simply skipped.

Ultimately, these resources should be made to safely handle asynchronous
shutdown, usually by panicking if called from a detached thread when the
main thread is ending.

See issue for details rust-lang#20012

This is a [breaking-change] for anyone relying on `at_exit`.
bors added a commit that referenced this pull request Dec 19, 2014
This PR substantially narrows the notion of a "runtime" in Rust, and allows calling into Rust code directly without any setup or teardown. 

After this PR, the basic "runtime support" in Rust will consist of:

* Unwinding and backtrace support
* Stack guards

Other support, such as helper threads for timers or the notion of a "current thread" are initialized automatically upon first use.

When using Rust in an embedded context, it should now be possible to call a Rust function directly as a C function with absolutely no setup, though in that case panics will cause the process to abort. In this regard, the C/Rust interface will look much like the C/C++ interface.

In more detail, this PR:

* Merges `librustrt` back into `std::rt`, undoing the facade. While doing so, it removes a substantial amount of redundant functionality (such as mutexes defined in the `rt` module). Code using `librustrt` can now call into `std::rt` to e.g. start executing Rust code with unwinding support.

* Allows all runtime data to be initialized lazily, including the "current thread", the "at_exit" infrastructure, and the "args" storage.

* Deprecates and largely removes `std::task` along with the widespread requirement that there be a "current task" for many APIs in `std`. The entire task infrastructure is replaced with `std::thread`, which provides a more standard API for manipulating and creating native OS threads. In particular, it's possible to join on a created thread, and to get a handle to the currently-running thread. In addition, threads are equipped with some basic blocking support in the form of `park`/`unpark` operations (following a tradition in some OSes as well as the JVM). See the `std::thread` documentation for more details.

* Channels are refactored to use a new internal blocking infrastructure that itself sits on top of `park`/`unpark`.

One important change here is that a Rust program ends when its main thread does, following most threading models. On the other hand, threads will often be created with an RAII-style join handle that will re-institute blocking semantics naturally (and with finer control).

This is very much a:

[breaking-change]

Closes #18000
r? @alexcrichton
@bors bors merged commit 903c5a8 into rust-lang:master Dec 19, 2014
@bluss
Copy link
Member

bluss commented Dec 19, 2014

spawn() and other adjacent features like channel() were in the prelude, saying something to the new Rust user about Rust the language in the process. I think it would be nice to preserve that, the feel of being setup up for concurrency by default.

@reem
Copy link
Contributor

reem commented Dec 19, 2014

It also sort of suggested that they were builtin constructs, which I think their removal from the prelude is supposed to prevent.

@aturon
Copy link
Member Author

aturon commented Dec 19, 2014

@bluss You might want to leave a comment at rust-lang/rfcs#503, which talks about the future of the prelude.

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.

implement a new task spawning model not depending on a managed runtime