-
Notifications
You must be signed in to change notification settings - Fork 83
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
Undefined behavior invoked by moving stacks between threads #6
Comments
I didn't see UB by running the following code. #[macro_use]
extern crate may;
use std::cell::RefCell;
use std::rc::Rc;
use std::thread::ThreadId;
use may::coroutine;
use coroutine::yield_now;
coroutine_local!(static ID: RefCell<Option<Rc<ThreadId>>> = RefCell::new(None));
fn main() {
may::config().set_io_workers(4).set_workers(4);
let h = coroutine::spawn(move || {
let v = (0..10000)
.map(|i| {
coroutine::spawn(|| {
let handle = Rc::new(std::thread::current().id());
ID.with(|id| {
*id.borrow_mut() = Some(Rc::clone(&handle));
});
for _ in 0..10000 {
if *handle != std::thread::current().id() {
println!("Access to Rc content without a mutex from a different thread, {:?} vs {:?}",
*handle, std::thread::current().id());
}
yield_now();
}
})
})
.collect::<Vec<_>>();
for i in v {
i.join().unwrap();
}
});
h.join().unwrap();
} |
Unfortunately, you cannot bypass the TLS problem in Rust. Because libstd uses My |
What do you mean, by „seeing“ UB? No, this code doesn't contain UB itself, only points to a place where it could happen. But if I, for example, added Note that UB can do anything. That includes pretending to work correctly when you watch. That's the tricky part about UB ‒ you don't have to see it, it can be lurk in the program and do arbitrarily bad things only from time to time (when the customer watches). And I know I don't want to use TLS from coroutines. The problem is, a blog post is not the appropriate solution. TLS isn't the only problem, there are others ‒ system calls that are expected to happen on specific thread, FFI libraries, etc. You never know if a library you use might be using TLS and not telling you. You are not supposed to have to watch for these things in Rust if you're not using If your library exposes a „safe“ interface which allows your user to invoke UB by it, the library is broken and dangerous to use. So you can either ensure the UB can never happen (by eg. not moving coroutines between threads after they are created), or mark the |
@zonyitoo I see this problem now. I have some questions about the issue:
|
Also, because Rust is a system programming language, libraries that contains FFI calls to some existing C/C++ libraries dominated most of the Rust's world. You may can control all your libraries to be "thread_local free", but you cannot control all those external libraries in dependencies. At least, you have no idea when libstd add another I have worked on stackful coroutine with work-stealing scheduler for almost 2 years, and I have already found that this is a dead end. I have tried to
I am not damping your enthusiasm of making coroutine libraries, but I have to tell you that the way you are going is not very promising. Feel free to contact me, I think we will have lots of common topics. |
@Xudong-Huang We've actually been discussing your crate and its issues in the Rust China community QQ group. We'd like to invite you to join our discussion, please add this QQ group |
@KiChjang thanks! will see you there. |
I use following test and can't see any problem, maybe the test is too simple. extern crate may;
use std::sync::Arc;
use may::sync::Semphore;
fn main() {
let sem = Arc::new(Semphore::new(10000));
may::config().set_workers(100);
loop {
sem.wait();
let s = sem.clone();
may::coroutine::spawn(move || ->() {
s.post();
may::coroutine::yield_now();
panic!("i'm dying");
});
}
} |
Access TLS in coroutine is not safe unless: Before schedule another coroutine the value of the TLS is a fixed one, so that schedule coroutine at any thread has the same TLS value, which is safe. Just like calling panic, before you schedule another coroutine you must have catch the panic, and at that time the |
I upload may-caveat doc. I think calling The basic goal of I will close this issue. But if you find some |
I'd like to point out that this is not about just libstd (but I'll try to reproduce it with libstd). Anyway, if you really want to go the way to tell user „don't do this“ and „do this“, that's fine, but you should make corresponding methods unsafe, to ensure they read and follow the instructions. That's the purpose of |
@Xudong-Huang is it fair to say that we don't have to avoid TLS use as long as we run may with one thread only? Can we update the Another question (sorry this is kind of unrelated) is, when calling libraries from a MAY coroutine, I have to make sure the library uses MAY's IO functions rather than std IO functions, correct (e.g. should use may::net::TcpStream instead of std::net::TcpStream) ? |
I'm afraid not,
correct, and this is the first rule that list in |
this would mark the core APIs as unsafe, which would
and I don't have a plan to change API specs. |
Your API already is unsafe. Pretending it's safe won't make it so, it'll only hurt users. Very good explanation about unsafe in rust was publish just recently: https://manishearth.github.io/blog/2017/12/24/undefined-vs-unsafe-in-rust/. To cite:
|
@vorner You are right. Thanks for the sharing |
It's also very annoying to encounter UB in 'safe' code... |
thanks all, I'm considering change the api spec to add |
A coroutine's stack can be moved from one thread to another. However, a stack may always contain things that are not
Send
, breaking things.This is one example:
If something else was accessing the
Rc
(like making copis of it) from the original thread (which it could, because it is accessible in the thread local storage), it would be undefined behaviour ‒ both the coroutine, that moved, and the thing in that thread (possibly other coroutine) could be accessing the counters in the Rc at the same time, or the data inside, which could be for example aRefCell
.Now, suggesting not to use thread local storage doesn't solve anything, because:
Send
for other reasons. One example might be Zero-MQ sockets, which explode the whole application if ever touched from a different thread then they were created in.I believe this problem is fundamental to any attempt to move stacks between threads in Rust. Such thing just breaks the Rust contract.
So my only suggestion is to create the coroutine in one thread (it is possible to check nothing
Send
crosses a closure boundary, so the closure can be safely sent to another thread) and then pin it there to that thread.The text was updated successfully, but these errors were encountered: