Single thread race condition and single threaded mut globle variables. #1065
Replies: 2 comments 1 reply
-
Your example isn't a race condition and can be easily replicated with a local variable as well. You're changing the order of operations, and the outcome of the program changes as a result. This is a pretty fundamental aspect of how C like programming languages work and I don't believe that is in scope for change in cpp2.
As you mentioned before a race condition occurs when two threads are operating at the same time, and it is a race to discover which one completes first/second at runtime, the outcome of the program becomes indeterminate because factors external to the program (OS level threading, core count, chance) can change the order in which the threads complete, and aren't necessarily going to be consistent between runs.
Your single threaded example has no race, as there is no concurrent modification of a value, simply two statements that set a value. Changing the order of statements is always expected to change the outcome in this case, and the program is determinate, you can run it on any system and repeatedly get the same output. Even if your example is more complex, and the lines that change the global are buried within different types, the single threadedness will still lead to the same behaviour.
That said, you've highlighted an issue, mutable globals are highly risky, as they have potentially far reaching side effects. This means they are considered bad practice. Rust may prohibit them, but C++ is about giving developers tools, and letting them decide how they'll use them.
A good improvement that I believe has been talked about already is delayed initialisation of const values.
(psuedo code)
foo : const Foo;
if x
foo = bar;
else
foo = baz;
foo.swizzle();
In this example foo is marked as uninitialised and the program is ill formed as long unless there is a 100% guarantee that it is initialised before first use.
Though I'm not sure how easily that could be applied to a global value.
On 12 May 2024 08:12:37 YagaoDirac ***@***.***> wrote:
Idk if this is already solved in cpp2, but I don't believe most people understand this problem correctly and enough.
What is single thread race condition:
The reason race condition exists is because in computer, data can only be copied to register and modified and then copied back to memory. If 2 thread copy the same data, increase, then copy back, it's a race condition. But this problem doesn't have to rely on multi thread environment. If we do the same thing in single thread (you have to write some extra code to create this bug), it also happens in similar way and harm in the exactly same way.
In SAFE Rust, Idk how to create this single thread race condition. I tried it with unsafe, it's really unsafe. You only need the unsafe scope to modify the mut globle variables.
An example and why it's dangerous:
Sorry for all the bugs in the code:)
static mut GLOBAL_VAR:...
fn main(){
//calc new result
//store new result back into GLOBAL_VAR
modify_global();
}
Assuming we already implemented the modify_global function. It modifies the globle_var.
Sometimes, it's very easy to get confused with the sequence of all the lines, for idk what reason. Actually sometimes, it's unavoidable. Idk, maybe I'm noob.
The result can be:
fn main(){
//calc new result
modify_global(); // This line is moved up
//store new result back into GLOBAL_VAR// this line down.
}
This is a very typecal single thread race condition. But luckily, in SAFE Rust, globle is not allowed.
What is the real problem? Idk how much people hate Rust's safety features. But I know most people don't really understand the danger here. Most of them simply wrap the code with unsafe when they want to modify the globle variables, without any necessary manual checking. In the previous case, most people simple go the unsafe way, and never check after move the lines.
How to write purely safe Rust with mutable globle variables:
//No real mut globle static at all.
fn main(){
let mut var global_var:...
//... not important
modify_global(&mut global_var); //Notice the param list
//... not important
}
You have to pass the mut ref into the function. In Rust, to do that, you have to drop (with scope or the drop function) all the ref to the global_var, and regain them after the call to modify_global function.
But the problem is, every function has to keep the param there, unless itself and all the function it's gonna call don't care the global_var at all.
A naive suggestion:
A flag may help, like, no_global:
void func(){
//you can use globle here.
//This is by default.
}
void func()[no_global]{
//you CAN NOT use globle here.
}
If we do this in Rust, we have to care the timing of dropping ref. I know it's a bit tricky, but it's never any trickier than other cases people handle everyday.
—
Reply to this email directly, view it on GitHub<#1065>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQPMDSFMACCUST7DIYTZB4I6HAVCNFSM6AAAAABHSSMKN2VHI2DSMVQWIX3LMV43ERDJONRXK43TNFXW4OZWGY2TANRWGM>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Thanks! Right, single-thread reentrancy gives many (not all) of the same problems as data races, and it's been with us forever but comes up less often so it's less well known. I have to teach this reentrancy case in my full Effective Concurrency class even though it has nothing to do with concurrency. Rust's solution relies on the borrow checker to guarantee unique mutability, which also covers this case. I'm not currently planning a borrow checker for Cpp2 though, at least not in the near term. One thing that's on Cpp2's roadmap that will help (at least partially) is that I plan to support running reflection+generation metafunctions on functions (not just types) and the plan is that those can do things like ensure no global variables are accessed. The usual example of a function metafunction is However, "doesn't access globals" isn't enough... One place this comes up is when I'm inside a function that is using an object In related news: Reentrant mutexes are almost never (not never, just "almost" never) what you want. Especially if the person reasons that "well I already hold the mutex so acquiring it again must be benign," then definitely the person hasn't thought about reentrancy... the second attempt to acquire the mutex is a different (nested) transaction on the shared state and will assume the shared state is consistent (obeys its invariants) which may not be true if the outer transaction that first got the mutex has made partial updates to the same data and is partway through taking it from one consistent state to another. |
Beta Was this translation helpful? Give feedback.
-
Idk if this is already solved in cpp2, but I don't believe most people understand this problem correctly and enough.
What is single thread race condition:
The reason race condition exists is because in computer, data can only be copied to register and modified and then copied back to memory. If 2 thread copy the same data, increase, then copy back, it's a race condition. But this problem doesn't have to rely on multi thread environment. If we do the same thing in single thread (you have to write some extra code to create this bug), it also happens in similar way and harm in the exactly same way.
In SAFE Rust, Idk how to create this single thread race condition. I tried it with unsafe, it's really unsafe. You only need the unsafe scope to modify the mut globle variables.
An example and why it's dangerous:
Sorry for all the bugs in the code:)
static mut GLOBAL_VAR:...
Assuming we already implemented the modify_global function. It modifies the globle_var.
Sometimes, it's very easy to get confused with the sequence of all the lines, for idk what reason. Actually sometimes, it's unavoidable. Idk, maybe I'm noob.
The result can be:
This is a very typecal single thread race condition. But luckily, in SAFE Rust, globle is not allowed.
What is the real problem? Idk how much people hate Rust's safety features. But I know most people don't really understand the danger here. Most of them simply wrap the code with unsafe when they want to modify the globle variables, without any necessary manual checking. In the previous case, most people simple go the unsafe way, and never check after move the lines.
How to write purely safe Rust with mutable globle variables:
You have to pass the mut ref into the function. In Rust, to do that, you have to drop (with scope or the drop function) all the ref to the global_var, and regain them after the call to modify_global function.
But the problem is, every function has to keep the param there, unless itself and all the function it's gonna call don't care the global_var at all.
A naive suggestion:
A flag may help, like, no_global:
If we do this in Rust, we have to care the timing of dropping ref. I know it's a bit tricky, but it's never any trickier than other cases people handle everyday.
Beta Was this translation helpful? Give feedback.
All reactions