Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upChange thread local variables to only accept async-signal-safe types. #1379
Conversation
eefriedman
reviewed
Nov 24, 2015
| by either `Cell` or `RefCell`. | ||
|
|
||
| `RefCell` can be fixed easily: Since there already is a locking mechanism, one | ||
| only has to ensure that the locking instructions will actually be emitted. Since |
This comment has been minimized.
This comment has been minimized.
eefriedman
Nov 24, 2015
Contributor
Just emitting the locking instructions isn't sufficient; we would actually have to use atomic increment/decrement. (An async signal handler could increment the borrow count of a RefCell by stashing the return value of borrow() into a global.)
This comment has been minimized.
This comment has been minimized.
eefriedman
Nov 24, 2015
Contributor
Hmm... correction; it's okay for thread_local!() because it's impossible to stash away a reference to a thread-local variable. (You end up with potentially wacky behavior if you leak a Ref in a signal handler, but not unsafe behavior.)
It's not obvious that property will still hold for #[thread_local], though.
Even if we don't need an atomic increment, I'm not sure it's appropriate to completely ignore the performance cost of the optimization barrier.
This comment has been minimized.
This comment has been minimized.
mahkoh
Nov 24, 2015
Author
Contributor
It's not obvious that property will still hold for #[thread_local], though.
Without a 'thread lifetime #[thread_local] variables are already unsafe because they pretend to be 'static.
Even if we don't need an atomic increment, I'm not sure it's appropriate to completely ignore the performance cost of the optimization barrier.
It's certainly easy to write another type that replaces RefCell if this is true. In this case the fix would be to replace the name RefCell by the name of its signal-safe replacement.
This comment has been minimized.
This comment has been minimized.
mahkoh
Nov 24, 2015
Author
Contributor
I believe the problem with forgetting borrows or putting them into globals can be fixed without atomic operations. RefCell currently looks like this:
pub struct RefCell<T: ?Sized> {
borrow: Cell<BorrowFlag>,
value: UnsafeCell<T>,
}
impl<T: ?Sized> RefCell<T> {
pub fn borrow(&self) -> Ref<T> {
match BorrowRef::new(&self.borrow) {
Some(b) => Ref {
_value: unsafe { &*self.value.get() },
_borrow: b,
},
None => panic!("RefCell<T> already mutably borrowed"),
}
}
}And it can be made safe like this:
pub struct RefCell<T: ?Sized> {
locked: Cell<bool>,
borrow: Cell<BorrowFlag>,
value: UnsafeCell<T>,
}
impl<T> RefCell<T> {
pub fn borrow(&self) -> Ref<T> {
if self.locked.get() == false {
self.locked.set(true);
barrier();
if let Some(b) = BorrowRef::new(&self.borrow) {
barrier();
self.locked.set(false);
return Ref {
_value: unsafe { &*self.value.get() },
_borrow: b,
};
}
}
panic!("RefCell<T> already mutably borrowed");
}
}But since this is a more complicated fix than adding a single barrier, it might be better to replace RefCell by another type. Note that the code above can be written better with the correct abstractions:
pub struct RefCell<T: ?Sized> {
borrow: SingleThreadMutex<BorrowFlag>,
value: UnsafeCell<T>,
}
impl<T> RefCell<T> {
pub fn borrow(&self) -> Ref<T> {
match BorrowRef::new(self.borrow.lock()) {
Some(b) => Ref {
_value: unsafe { &*self.value.get() },
_borrow: b,
},
None => panic!("RefCell<T> already mutably borrowed"),
}
}
}But a SingleThreadMutex that is general purpose might be slightly less efficient than the previous version.
This comment has been minimized.
This comment has been minimized.
mahkoh
Nov 24, 2015
Author
Contributor
An async signal handler could increment the borrow count of a RefCell by stashing the return value of borrow() into a global.
It actually can't because globals cannot contain types with destructors. This means that the more complicated fix above is unnecessary and the original proposal in the RFC should work.
This comment has been minimized.
This comment has been minimized.
|
I wrote a bit async signal handlers in the standard library here: rust-lang/rust#30003 (comment) . Basically, I don't see safe async signal handlers ever happening in the Rust standard library in the way you're proposing. One more thing worth pointing out: if you're going to throw out POSIX, there isn't any reason to have async signal handlers run on an existing thread. |
This comment has been minimized.
This comment has been minimized.
This RFC does not propose adding any kind of async signal handling to the standard library (see also the last alternative.) It proposes adding the right tools to the language to make all rust code async signal safe by default as long as all the ffi code it calls is async signal safe. The language already does exactly the same with thread-safety. Is there more code out there that is async signal unsafe than there is code out there that is thread unsafe? Very likely. Is it possible to use the language in a way that only interacts with async signal safe ffi code? Definitely. Is it possible that, in the future, more ffi code will be written that is async signal safe? Certainly if this RFC is accepted. If K&R had made C async signal safe by default 40 years ago, then we would not be having this discussion.
Sounds like an incorrect generalization.
|
This comment has been minimized.
This comment has been minimized.
wthrowe
commented
Nov 24, 2015
|
The code proposed here doesn't compile. As far as rustc is concerned, the " Even if the language were modified sufficiently so that these impls did not conflict, under the current implementation of OIBITS neither the default impl nor any of the negative impls ever applies. Aside from the compilation errors, I believe this is equivalent to unsafe trait Interrupt {}
unsafe impl<T: Sync> Interrupt for T {}I continue to oppose proposals that add OIBITS until we at least have some sort of consensus on whether the current implementation is a bug or not. A comment on the RFC text itself: I believe this is proposing that |
This comment has been minimized.
This comment has been minimized.
You have to put it in libcore. Otherwise it won't compile. I've already implemented it in lrs and it compiles just fine there. But maybe this is a bug and it should never compile?
Give the performance concerns @eefriedman has, it is not clear whether RefCell should be fixed or if the library should be supplemented by another type that is signal safe. It is possible to implement a SingleThreadMutex that does not have the move-into-global problem mentioned by @eefriedman above and that compiles down to a single branch:
|
This comment has been minimized.
This comment has been minimized.
wthrowe
commented
Nov 24, 2015
Ah, no, you're right. I forgot part of the coherence rules. I still believe the negative impls don't actually do anything, though. |
This comment has been minimized.
This comment has been minimized.
The simple two-liner you posted above might actually be the better implementation since unsafe trait Interrupt {}
unsafe impl<T: Sync> Interrupt for T {}And then one just has to add additional positive implementations for things that are not quite Edit: Nope, I was wrong. Not having a |
This comment has been minimized.
This comment has been minimized.
|
Since the previous version was too complicated I've reduced the RFC to its core. The old version can still be accessed via the commit history. The new version only deals with the language aspects of async-signal-safety: The types that can be the type of a
This allows people who are more familiar with the constraints of different libraries to decide how the distinction between unsafe impl Interrupt for .. { }without any negative implementations might be the way to go. The RFC now no longer proposes any breaking changes. |
mahkoh
force-pushed the
mahkoh:master
branch
from
f816ce1
to
3b9f48e
Nov 27, 2015
mahkoh
force-pushed the
mahkoh:master
branch
from
3b9f48e
to
ee2e845
Nov 27, 2015
alexcrichton
added
the
T-libs
label
Nov 30, 2015
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton I believe this should be T-lang and not T-libs. |
alexcrichton
added
the
T-lang
label
Nov 30, 2015
nikomatsakis
self-assigned this
Jan 7, 2016
This comment has been minimized.
This comment has been minimized.
|
Overall, I am interested in addressing signal handling in a more first-class fashion. It's come up also in the context of embedded devices. This also seems quite related to the idea of cooperative multitasking on a single thread, which has looser requirements than true multithreading, and is also quite useful in embedded devices and so forth. This is obviously related to the general topic of this RFC, though it's not clear that the actual trait proposed here would be especially helpful for anything other than |
This comment has been minimized.
This comment has been minimized.
|
I have not but I think the trait (as formulated in the RFC) can also handle voluntary context switches. What it cannot handle are general involuntary context switches, e.g. userspace scheduling via signals. |
This comment has been minimized.
This comment has been minimized.
|
|
alexcrichton
added
the
final-comment-period
label
May 24, 2016
This comment has been minimized.
This comment has been minimized.
CodesInChaos
commented
Jun 2, 2016
|
Could you explain why thread-safety ( |
This comment has been minimized.
This comment has been minimized.
|
What types are |
This comment has been minimized.
This comment has been minimized.
|
The libs team got a chance to discuss this RFC yesterday and the conclusion was that we're going to close this for now. As @nikomatsakis mentioned it definitely seems worthwhile to ensure that we support signals and such nicely, but at this time we think we may want to flesh out the story there a little more thoroughly as well. Thanks regardless though for the RFC @mahkoh! |
mahkoh commentedNov 23, 2015
Interrupttrait for thread-local objects that can be usedfrom signal handlers.
#[thread_local]statics fromSynctoInterrupt.