-
Notifications
You must be signed in to change notification settings - Fork 59
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
What is the opsem of signal/interrupt handlers. #444
Comments
You said in the meetings there are examples where treating a signal handler as an independent thread causes not just liveness issues but UB? |
In C you did have to use volatile reads and writes, |
Do you? Even in C concurrent non-atomic reads of the same data from multiple threads are allowed. So I would expect "concurrent" non-atomic reads of the same data from a thread and its signal handler to be equally permitted. |
The most direct examples are "Call an AS-unsafe function". I don't know a specific example of pure-rust, but I bet I could come up with one.
The const int X = 0;
You would think. Technically, it would have an unspecified value. I'd be quite shocked if that value was any different from it's initialized value, though. |
C++17's definition does fix that, by redefining it's access model in terms of it's MT Memory Model (and constant initialization happens-before every evaluated statement in the entire program). |
For a non-atomic value you did have a data race without volatile or a signal fence and with an atomic value you did miss synchronization between the store and the start of the signal handler (so the signal handler could run before the store) or between the end of the signal handler and the load (so the signal handler could run after the load) unless you explicitly synchronize in some way for an async signal handler as far as I understand. For atomics this is UB free, but likely not what you want. On the other hand for sync signals I did expect a synchronization to happen between the function raising the signal and the signal handler itself, but it seems to not be guaranteed. https://en.cppreference.com/w/c/program/signal
|
Do keep in mind that my statement was about non- That being said, your analysis is correct as to C (though, as I mentioned, C++17 would allow the C++ equivalent). |
@bjorn3 We are specifically talking about the read-only case. There are no stores. Without stores, there cannot be data races nor missed synchronization. |
@bjorn3 is correct though, that in C it would have an unspecified value absent actual synchronization with a proper fence or atomic operation. I think it's reasonable to adopt something closer to the C++ definition, though. |
That seems like an oversight that was fixed by C++17 properly integrating this into their concurrency model. |
Perhaps. I haven't heard of a proposal fixing it for C23, though, so the earliest it fixes in C is going to be 2029. For rust, though, I"d definitely go with C++'s model, though. Frankly, seems easier to make operational before even considering what it implies. |
Another interesting question for signal handlers is the FP environment. I think we want to allow inline assembly blocks to change the FP unit to a different state, perform some operations, and then change it back. However that means we must declare float operations as not-async-signal-safe, since the signal handler might encounter the FP environent in a non-default state which leads to Rust floating-point operations executing incorrectly. |
Or is that handled by the register save/restore logic when "context-switching" between the actual thread and the signal handler? |
Based on some testing, it varies:
So indeed, a portable signal handler should assume the FP environment is in an unknown state, and make sure it's still in that state before returning. After all, it's not just inline assembly that might temporarily change the FP environment, but also external code called through FFI. |
I suspect that counting interrupt handlers and signal handlers as just one category is too much of a simplification. Putting it perhaps overly broadly, interrupt handlers are usually used in a no_std environment when a hardware situation occurs, while signal handlers are usually used in a std-like environment when a software situation occurs. I have less experience with signal handlers, but it seems just from reading this thread like they have more restrictions than what an interrupt handler has to think about. Should we try to split up the situation? |
I'd argue the restrictions have less to do with the actual differences between interrupt handlers and signal handlers, and more to do with the fact that C and POSIX specs try to specify signal handlers semi-formally and portably at the C level, while interrupt handlers are non-portable and are only specified at the assembly level. For example:
However, since we're trying to define high-level language semantics, and aren't focusing on a specific architecture, I don't think there's much advantage in splitting up the two topics. |
Side note: Windows doesn't have signals or anything really equivalent. The C standard library does emulate them to the extent necessary but that's all it is. |
Both C and C++ say that the floating-point environment is unspecified upon entry to a signal handler, and doesn't make changing it signal-safe. I think that the only option is indeed to say that floating-point operations are UB inside an asynchronous signal handler. |
Signal Handlers (and interrupt handlers, which are basically equivalent) are a fun thing to specify.
Signal Handlers have been modeled as separate threads, but this is incorrect: Because the code that is interrupted is running on the same thread, you cannot, e.g. acquire a mutex, or perform I/O from a signal handler. (C, C++, and POSIX each individually maintain a more comprehensive list of what you can and cannot do: https://en.cppreference.com/w/cpp/utility/program/signal, https://en.cppreference.com/w/c/program/signal, https://man7.org/linux/man-pages/man7/signal-safety.7.html).
Some things that are clear, at least to me:
mut
,impl Freeze
statics should be accessible and yield proper values when read,mut
statics withAtomic*
types should be accessible and be usable as normal,Note: This question only tracks asynchronous signals and asynchronous interrupts. I'd assume the answer to "What can a signal caused by
raise
do" is "Anythingraise
could do if it was an arbitrary opaque function".Further sub-questions:
The text was updated successfully, but these errors were encountered: