Google Is Uncovering Hundreds of Race Conditions Within the Linux Kernel
phoronix.com
phoronix.com
This is because one of the sources of very hard to reproduce bugs is a set of race conditions aligning just right.
[0] https://en.wikipedia.org/wiki/Metastability_%28electronics%2...
The Zebra test is a good friend.
The inputs to "software" finite state machines are typically inputs in the form of variables. Those inputs define which branches within the routine will be taken during processing.
You can model a subroutine as an FSM for which its "outputs" are its state, the FSM takes inputs, processes them, and sets new outputs or a new state.
We use fuzzing to expose the state machine to all possible combinations of inputs and this identifies all possible exit states from all possible input states, but assumes the input states are stable.
Multi-processing introduces the possibility of race conditions. In a race condition, one input state is present when the state machine is entered, but during execution the race completes which changes the input value.
We use 'stable' to define an input that has the same value across the entire duration of the state machine's state execution.
During a race condition, an input may change value one or more times across the time interval of the state machine's state execution. Thus at any instant in time the input has a singular value, during an interval in time that value may have many different values. These inputs are meta-stable.
And yes, you can fix meta-stability in software with things like mutexes and execution exclusion. Just like you can fix meta-stability bugs in hardware signals by add a clock synchronization domain that spans the widest period of meta-stability possible for a signal.
One of the things that makes race condition bugs so hard to debug is that your typical tracing facility assumes that the inputs passed to a function are stable and don't change. Thus you'll see a trace record of a function call, with its parameters, and walk through the code and say "Wait, with those parameters this code could never do what it just did." Or conversely, that variables that have shared write status across execution domains will be the same throughout a function.
Does that make is clearer what I was talking about? Race conditions suck :-)
https://software.rajivprab.com/2019/04/28/rethinking-softwar...
Does this automatically generate fixes too, or does someone need to investigate time for each by hand?
>kcsan-with-fixes: Contains KCSAN with various bugfixes for races detected; the commit messages for those bugfixes include the KCSAN report as-is.
Which to me seems to imply some sort of automatic mitigation. Maybe I'm reading too much into it
Still, I think from my interest point of view it would be interesting to not only understand the Linux kernel better, but also other OS design paradigms.
Like... Windows?
Of course, it's not much of an option for anything I require proprietary software.
Also it’s pretty easy to copy the C stuff to new OSes as well for portability.
I use macOS all the time. My use-case is systems programming.
I also uses iOS on my phone.
Or is that not what you meant?
"Mac OS X is sort of microkernelish. Inside, it consists of Berkeley UNIX riding on top of a modified version of the Mach microkernel. Since all of it runs in kernel mode (to get that little extra bit of performance) it is not a true microkernel, but Carnegie Mellon University had Berkeley UNIX running on Mach in user space years ago, so it probably could be done again, albeit with a small amount of performance loss, as with L4Linux. Work is underway to port the Apple BSD code (Darwin) to L4 to make it a true microkernel system." [1]
Can run Linux without virtualization (native performance via syscall translation) in a zone if I hit any issues, which I’ve not yet (simple web stack).
Google around for some of Bryan Cantrill’s talks on smartos. Solaris derivative. He’s been quite critical of several linux engineering decisions and what he frames as sloppy thinking in the kernel community.
I found this helpful/inspiring - https://timboudreau.com/blog/smartos/read - if you follow his example be sure to change dataset_uuid to image_uuid in the zone manifest.
I have both macOS and Ubuntu on my MacBook. I have always predominantly used macOS for all my day-to-day uses and only use Ubuntu to test software that needs to run on Linux before releasing.
I have known many people who use FreeBSD or OpenBSD daily and I have even heard some people installing Haiku as a secondary OS because they have now found it 'very interesting' to use.
I don't know anyone using MINIX though, but I believe that Fuchsia looks like one of the most technically interesting new OSes to be developed in terms of OS design paradigms.
And many many others.
> You can have data races in Python, Javascript and Rust as easily as you can in C.
Don't use "race conditions" and "data races" interchangeably if you understand the difference...
EDIT: windows famously had bugs and had to add special code to preserve the buggy behaviour to keep certain applications that relied on them working https://www.joelonsoftware.com/2004/06/13/how-microsoft-lost...
Part of the job of a kernel is to resolve races. Requests come to it from multiple threads and processes in a parallel fashion. Sometimes your timing will be one way and get you one result, sometimes it will go another way. That's OK. It's the nature of the beast.
Perhaps a read not seeing someone else's write in a timely fashion is OK in your situation. Perhaps you will resolve conflicts later using proper synchronization.
You had my retort already written. Thank you.
And in fact the linux kernel does rely on very specific compilers, and breaks the standard's machine abstraction quite a bit.
Considering that C didn't support threading or even an atomics API until C11, and that most C code was written well before C11, by your definition the entire Linux kernel has been one giant "bug" ever since it added SMP support.
Are data race conditions bugs? Usually--the term itself is conclusory of that fact. They're bugs not because they constitute undefined behavior as defined by the C standard, but because neither the hardware, compiler, application logic, or anything else guarantees consistent behavior; and if you have no guarantee of behavior then your software is ipso facto incorrect. If something were to guarantee the behavior, such as the notably generous cache coherency semantics of the x86 ISA, and nothing else conflicts with that guarantee, like an aggressive compiler, then it wouldn't be a bug, at least to the extent you knowingly relied on that guarantee.
Undefined behavior is not an epithet. Just because a language specification doesn't use the term doesn't mean it doesn't have undefined behavior. Gobs of code in languages like Python and PHP rely on undefined behavior because those languages have very loose specifications. Even Java has undefined behavior despite its otherwise extremely rigorous specifications. (What Java does have is a very generous memory model which usually cabins the consequences of undefined behavior. Even then you can never rule out undefined behavior resulting in nasal daemons through some cascade of unexpected code paths in the application.)
One of the reasons the term is so important in C and C++ is because they're two of the few languages with numerous, diverse implementations. The odds of two implementations diverging while programmers unwittingly rely on one of several potential behaviors is much greater. If there are only a few relevant implementations then there's less pressure to explicitly distinguish undefined behavior. Behaviors will tend to converge through informal communication. There's almost no pressure beyond the good diligence of the engineers if there's only a single implementation. Any accidental semantics relied upon by users can be made retrospectively well defined, and if there's no reliance then behaviors silently change (sometimes breaking code).
For reference, here's what undefined behavior means, per C11 3.4.3 (N1570):
1 undefined behavior
behavior, upon use of a nonportable or erroneous
program construct or of erroneous data, for which this
International Standard imposes no requirements
2 NOTE Possible undefined behavior ranges from ignoring
the situation completely with unpredictable results, to
behaving during translation or program execution in a
documented manner characteristic of the environment
(with or without the issuance of a diagnostic message),
to terminating a translation or execution (with the
issuance of a diagnostic message).
3 EXAMPLE An example of undefined behavior is the
behavior on integer overflow.Even if there is parallelism and multi-core, it is still a question of ordering, and from there we can use language like who gets there "first" [indeed "race" suggests this]. Or, did this read of a machine word see somebody else's write, etc.
You can even have race conditions in hardware. Multi-threading or distributed software are excellent recipes for the introduction of race conditions, sometimes so subtle that the code looks good and the only proof that there is something bad going on is the once-in-a-fortnight system lockup.
#Sarcasm
In a - properly implemented - micro kernel you will have each of those drivers isolated in its own user process, and if there is an issue (memory corruption, crash, logic error or race condition) the possibility of its effects spreading is limited.
Isolation is half the battle when dealing with issues such as these.
Because I don't see the advantage here. So what if my filesystem driver is isolated, a race condition in there is still going to corrupt all my data and I'm not going to be cheered up by the fact that the kernel can keep on ticking.
Another is to think of this as layers of essential services: kernel, memory management, networking services, file services and so on. For a layer to be reliable the layers below it have to be reliable too. Isolating the layers in processes allows for faster resolution of where a problem originates which in turn will help to solve it.
And being able to identify the source of a problem allows for several other options: restart the process, proper identification of a longer term resolution because of the ability to log the error much closer to where it originated.
These are not silver bullets but structural approaches acknowledging the fact that software over a certain level of complexity is going to be faulty.
In my opinion the only system that gets this right is Erlang. (not the language, but the whole system)
is there any reason to believe that most (or even a just considerable part) of race conditions in a kernel are cross module?
One problem here is that it's not always a simple layer on layer order of dependencies, they can work both ways. Take swapping/paging for example. It can't be isolated as a separate driver or service. The kernel relies upon it, and so do processes, and yet the service itself relies upon filesystems, a separate layer again. A race condition in the swap system (like one of the bugs found by the tool in question) would break multiple layers, or a race in the filesystem could cause the swap system to fail. There is no real isolation here that saves an OS from failures.
One reason why race conditions in monolithic kernels are very common is due to the requirement that code is re-entrant because of multi-threaded execution of the kernel itself. In a micro kernel situation the multi-threading can be avoided because you have enough processes to spread the cpu cores over to have a number of them active at the same time without having two or more of them active within the same process.
having had to deal with race conditions between processes running at opposite side of a continent, color me unimpressed.
Less facetiously, some micro kernels have been proven formally correct. You need a small code to prove formally correct.
And even on an intuitive level, if the code is small I can "hold" it all in my head - at least really grock it in a way you can 1E6 loc.
Would also be cool for auto-complete suggestions. I'm thinking more along the lines of extracting larger patterns than for loops, such as stubbing out classes that follow a projects existing style. For example, adding an auth check at the start of a web request handler if that's what it sees elsewhere.
However, one way to avoid such a problem was basically settling for less accurate, but still strict subproblems. I.e. Rust doesn't stop all race conditions but solves them for a subset of data races.
In other words you reject some valid programs (false positive?) in order to make sure all valid programs are really valid (false negative?).
Here some resources:
The Wikipedia page: https://en.wikipedia.org/wiki/ATS_(programming_language)
"A (Not So Gentle) Introduction To Systems Programming In ATS" by Aditya Siram at the StrangeLoop 2017: https://www.youtube.com/watch?v=zt0OQb1DBko
Introduction to ATS, a series of screen casts: https://www.youtube.com/playlist?list=PL6BIXG1a4elsauhh56i5n...
I do not program in ATS because I am not doing anything that needs to be as efficient, so that I have the luxury to enjoy programming and learning Haskell, in the hope that by the time when I would need to write some super secure and performant program that has to run without interruption, and is used by more users than myself, well, I hope that by that time Haskell will evolve enough to allow me to write such a program. Otherwise I might end up using ATS, though honestly the syntax is so ugly.
https://runtimeverification.com/match/1.0-SNAPSHOT/docs/benc...
https://spectrum.ieee.org/computing/software/mayhem-the-mach...
The field evidence would support their position, too. Your claim about C is true just enough to encourage people to not use it if they want to get more out of program analysis. Yet, current tools make up for it enough to find most or all bugs in benchmarks with Mayhem fixing them, too.
The only problem is that almost nobody that cares about FOSS security is working on those tools that automate it. The companies doing it end up black boxing, patenting, etc the tools that they sell for exhorbitent prices. Something like RV-Match well-integrated with repos either FOSS or just price scaled to focus on mass adoption over high profit would be a game changer. Especially if 3rd parties could use it on repos.
https://www.cs.colorado.edu/~kena/classes/5828/s12/presentat...
https://cacm.acm.org/magazines/2010/2/69354-a-few-billion-li...
Here's the main competition evaluating them so you can see criteria and current state-of-the-art:
https://www.sosy-lab.org/research/pub/2019-TACAS.Automatic_V...
The provers do something similar but with more manual effort and logic-based approach. Frama-C and SPARK Ada are interesting in that they use programmer annotations plus supporting functions (eg ghost code). These are converted into logical representation (eg facts about the program), fed into logic solvers with properties intended to be proved, and success/failure maybe tells you something. Like with static analyzers, you get the results without running the program. A recent, great addition is that, if proof is too hard, the condition you didn't prove can be turned into a runtime check. You could even do that by default only proving the ones that dragged performance down too much. Flexible.
The best methods are a mix. I've always advocated mixing static analyzers, dynamic analysis, fuzzing etc since each technique might spot something the other will miss. They also cover for implementation bugs each might have. That adding runtime analysis can improve effectiveness does not negate my original claim that static, non-runtime analysis of C code can tell you a lot about it and/or find a ton of bugs. I also agreed with one point you made about it not conveying enough information: these clever tools pull it off despite C lacking design attributes that would make that easier. SPARK Ada is a great example of a language designed for low-level, efficient coding and easy verification. Although, gotta give credit to Frama-C people for improving things on C side.
https://www.win.tue.nl/~wstomv/edu/2ip30/references/design-b...
https://www.hillelwayne.com/post/contracts/
https://www.hillelwayne.com/post/pbt-contracts/
Many of us independently converged on a consensus about how to do it quickly, too. You put the contracts into the code, do property-based testing to test them, and have them turn into runtime or trace checks combined with fuzzers. Eclipser is one that runs on binaries finding problems way faster than others. This combo lets you iterate fast knocking problems out early. If you want more assurance, you can feed those contracts (aka formal specifications) into tools such as Frama-C and SPARK Ada to get proofs they hold for all inputs. Obviously, run lots of analyzers and testing tools on it overnight, too, so you get their benefits without just staring at the screen.
https://github.com/SoftSec-KAIST/Eclipser
Another lightweight-ish, formal method you might like was Cleanroom Software Engineering. It was one of first to make writing software more like engineering it. Stavely's introduction is good. His book was, too, with the chapter on semi-formal verification being easier to use and high ROI having excellent arguments. Fortunately, the automated and semi-automated tools doing formal analysis look good enough to replace some or all of the manual analysis Cleanroom developers do. Languages such as Haskell with advanced type systems might let it go even further. I advise coding in a simplified, hierarchical style like in Cleanroom to ease analysis of programs even if you do nothing else from the method. If you're curious, that technique was invented by Dijkstra in 1960's to build his "THE Metaprogramming System."
https://web.archive.org/web/20190301154112/http://infohost.n...
https://en.wikipedia.org/wiki/THE_multiprogramming_system
Have fun with all of that. I'm pretty sure straight-forward coding, careful layering, DbC, and PbT will help you on realistic projects, too.
Thank you so much! I've been looking into building a foundation with regards to formal methods and safety-critical systems, and besides getting familiar with the regulations and Standards around that type of software, I've also been looking to build up some familiarity with the tools to achieve those higher levels of provability!
This all looks like it fits the bill perfectly! Thank you! Thank you! Thank you!
Shhhhhh!!
"safety-critical systems"
Interesting enough, safety-critical field doesn't actually use formal methods much. It's more about lots of documentation, review, and testing. Mostly just human effort. They do buy static analyzers and test generators more than some other segments. There's slowly-slowly-increasing adoption of formal methods in that space via SPARK Ada and recently-certified CompCert. The main incentive so far is they want to get bugs out early to avoid costly re-certifications.
DO-178C and SIL's are main regulations driving it if you want to look into them. DO-178B, the prior version, is also a good argument for regulating software for safety/security. It worked at improving quality. We even saw our first graphics driver done robustly. :)
I'm going to have a look at your links more. I guess the question I don't have answered quite yet is how to tell the tool what to look for beyond the language semantics.
What pro's often do, though, is prove the algorithm itself in a higher-level form that captures its intrinsic details, create an equivalent form in lower-level code, and prove the equivalence. That lets you deal with the fundamentals first. Then address whatever added complexity comes in from lower-level details and language semantics.
Yes, these are both much higher level than C, but if what I'm reading among other comments is correct, Rust accomplishes an equivalent improvement in compile time checks while still being a systems language.
Rust is perfectly capable of expressing graphs with zero unsafe code, just currently not in the most optimum implementation.
Refcounting in Rust is still faster than refcounting in ObjC or Swift (because Rust can avoid using atomics or increasing counts in many cases), but people who use Rust tend to insist on zero overhead, and a truly flexible zero-overhead solution can't be verified statically.
You can make non-leaking pointer-based graphs in safe Rust using reference counting (basically what C# does, if you squint) or arenas (if your nodes' lifetimes fit that pattern).
It is a heck of a lot easier to not make them in Rust than in someting like C and C++. But Rust is not a panacea for asynchronous code.
* Data races
Rust does not prevent:
* Deadlocks
* Race conditions
* Memory leaks
* Logic bugs
That said, it does make some of these things harder to accidentally introduce, but strictly speaking does not prevent them, it's true.
However, it is possible to leak, e.g. if you use a reference-counted type and create a cycle. There's also Box::leak() that does what it says (it's useful for singleton-like things).
This is different from Rust's guarantees about memory and thread safety, which you can't break without `unsafe`.
So, not only can you do that: it's standard practice for some developers already in and out of commercial space. Mostly commercial developers doing it that I see, though.
Note: Linking to Saturn since it's old work by small team. I theorized big investment could achieve even more. Facebook's acquisition and continued investment with excellent results proved it.
If tech and money is the limit, tell me why Google hasn't straight-up bought the companies behind RV-Match and Mayhem to turn their tech on all their code plus open-source dependencies. Even if over-priced, it might actually turn out cheaper than all the bug bounties adding up. Maybe re-sell it cheap as a service Amazon-style. The precedent is that Facebook bought one that built a scalable tool they're applying to their codebase. Then, they were nice enough to open-source it. Google, Microsoft, Apple, etc could get one, too.
What's Google's excuse? They sure aren't broke or dumb. Gotta be cultural. Likewise, Microsoft built lots of internal tools. They do use at least two static analyzers: one for drivers, one for software. I heard they even ship 2nd one in VS. They don't use most of the MS Research tools, though. The reason is cultural: Operations side knows they'll keep making money regardless of crap security.
Far as FOSS developers, most don't care about security. Those that do tend to follow both what takes little effort (naturally) and whats trending. The trending topics are sanitizers, fuzzers, and reproducible builds. Hence, you see them constantly instead of people learning how to build and scale program analyzers, provers, etc with much better track record. Note that I'm not against the others being built to complement the better methods. I just think, if this is rational vs emotional, you'd see most volunteer and corporate effort going to what has had highest payoff with most automation so far. For a positive example, a slice of academics are going all-in on adaptive fuzzers for that reason, too. They're getting great results like Eclipser.
I also find it weird that you reference Saturn, since Alex Aiken hasn't been driving heavy SAT stuff for a while. Clark Barrett is at Stanford now and is doing more things related to what you want. And... oh wait he spent a few years at Google at few years ago.
Fuzzing plus sanitizers work like mad. They aren't magic and interpocedural static analysis provides value too. But your claim that Google is ignoring these techniques and doing so because of cultural idiocy just isn't correct.
https://cacm.acm.org/magazines/2018/4/226371-lessons-from-bu...
I'm saying they didn't care enough to do the kind of investment others were doing which would've solved lots of their problems.
The article also indicates they couldn't get developers to do their job of dealing with the alerts despite false positives. If Coverity's numbers are right, there's over a thousand organizations whose managers did it better.
Since they didn't address it, Google would be about the best company to acquire expensive tech like Mayhem that finds and fixes bugs so their developers can keep ignoring them. Alternatively, start double teaming the problem investing in FB's tool, too, moving in best advances from SV-COMP winners.
I mean, their size, talent, and finances don't go together with results delivered (or not) in static analysis. They could do a lot more with better payoff.
EDIT: Forgot to mention I referenced Saturn because parent said the methods couldn't scale to the Linux kernel. And Saturn was used on the Linux kernel over a decade ago. A few scale big now.
I obviously won't be able to convince you since they aren't publishing at the same rate as facebook. So you'll just have to take my word that Google doesn't have a vendetta against static analysis.
The Infer team is claiming to get results on C/C++ with both sequential and concurrency errors via a tool they open sourced. I value scalable results over theories, team counts, etc. Does Google have a tool like that which we can verify by using it ourselves? Even with restrictions on non-commercial use? Anything other than their word they're great?
"that it seems odd to pivot to Infer here." "So you'll just have to take my word that Google doesn't have a vendetta against static analysis. "
You really messed up on 2nd sentence since I linked an article on Google's static analysis work. I told people they're doing it. I mentioned Infer was Facebook pouring a lot of money into a top-notch, independent team to get lots of result. I mentioned Google could do that for companies like that behind Mayhem that built the exact kind of stuff they seem to want in their published paper. They could do it many times over. If they did it, I haven't seen it posted even once. They don't care that much.
Your claims about team size and "vendetta against static analysis" are misdirection used to defend Google instead of explain why they don't buy and expand tech like Infer and Mayhem. And heck, they can use Infer for free. My theory is something along the lines of company culture.
But the distinction between "runtime" and "compile-time" is not entirely binary. What you call "compile time" checks can be viewed as an abstract interpretation [1] of the program, or as running the program in some abstract domain -- e.g., where a concrete value could be 3, its abstract value could be called `int`, and the + operation could be interpreted as `int + int = int`; type inference could be viewed as abstract interpretation. The problem is that sound abstract interpretation (i.e., one without false negatives -- if it tells you your programs is "safe" then it will definitely be safe, for some appropriate definition of safe) is limited, and often comes with big tradeoffs, e.g. either false positives given by sound static analyzers or the pain something like Rust's borrow checker sometimes causes -- these are both instances of the same underlying difficulty with abstract interpretation.
One of the most promising directions in formal methods research is "concolic testing" [2], which means a combination of concrete (i.e. non-abstract, or "runtime") execution and symbolic execution (an instance of abstract interpretation). It is not sound, but can be made quite safe, and at the same time it can be very powerful and flexible, checking properties that can be either impossible or extremely tedious, to the point of infeasibility, with abstract methods like types. It might prove to be a good sweet spot between the power, expressivity and cost of concrete (AKA dynamic, AKA runtime) methods and the soundness of abstract (AKA static, AKA compile-time) methods.
Even dynamic methods are often "stronger" (more sound) than just testing assertions during testing. For example, fuzzers can sample the program's state space, offering more coverage, either by analyzing the program (whitebox) or just randomly (blackbox), and various sanitizers sample the scheduling space by fuzzing thread scheduleing.
Also, not all data races can be exploited into serious bugs (in some cases, some stats might just be incorrect, for example).
That doesn't make the tool useless, of course! Just that one should take the numbers with a grain of salt.
2) data races are undefined behavior in C, but:
3) they don't necessarily translate to bugs in practice. for example:
this is a data race, but in practice everything works as expected.
in cases like this, fixing the data race adds overhead without giving us much benefit.
HN is a community. Users needn't use their real name, but do need some identity for others to relate to. Otherwise we may as well have no usernames and no community, and that would be a different kind of forum. https://hn.algolia.com/?sort=byDate&dateRange=all&type=comme...
Anyway, back on topic: would fixing these race conditions be only beneficial to stability, or would this also improve performance/responsiveness?
Edit: it goes without saying, but it's much better to fix the race conditions, regardless of what it means for performance. Some people seem to think I'm advocating for not fixing them.
Great Highway in SF, if you drive 35MPH you will just get greens on the evenly spaced lights. The worst is when you get stuck behind a person who drives 40, then brakes heavily, then drives 40, then brakes heavily, etc.
Edit: (or go double the speed limit if you must but please stop creating stops)
If two cars both say they drive at 50 km/h, and one is actually 45 km/h and the other 55 km/h, that is quite a bit relative margin of error. And we only need two cars like that in the whole group to start causing problems.
If you can't design a green wave to be tolerant of varying speeds, then it isn't useful.
Timed lights work, I've seen it in action. Top of my head, Indianapolis 20 some years ago, Capitol Avenue going north I'd bet you could go from (what is effectively) Zero Street to 38th and not hit a red light.
Of course traffic was heavy enough that you'd be stuck behind someone doing the speed limit most days, but sometimes you got to ride the wave.
> In the UK, in 2009, it was revealed that the Department for Transport had previously discouraged green waves as they reduced fuel usage, and thus less revenue was raised from fuel taxes.
Ugh...
The degree by which governments routinely violate the accounting principle of matching revenues to expenses is awful.
A carbon tax on consumers (incl. businesses) is effective at producing good market behavior regardless of how the tax revenue is spent.
In the example I reacted to, a government agency actively worked to inhibit that behavior in order to preserve its revenue. Your point would hold if governments didn't have the power to do things like this.
> Earmarked funds are stuck.
If the revenue is being raised to pay for damage caused by society, those funds should be stuck to that purpose and should rise and fall based on the level of damage being done.
A governmental bad actor is basically orthogonal to the idea of a carbon tax. The government could equally try to increase sales of liquor and cigarettes for sin taxes, or go around murdering people for the estate taxes. The UK example is shameful but reflects on that government body rather than the specific tax on petrol.
The problem is earmarking or allocating a variable set of revenue as funding for something unrelated, and in particular something unrelated that's otherwise valuable or important.
And NOT earmarking or allocating 'sin taxes' to pay for the supposed costs of those sins, borne by the public in the form of the government, seems pretty stupid (to me) if it's actually necessary or desirable for the government to 'manage' those sins or those sin's consequences.
There is this thing called the "waiting time paradox" [0] (or more generally the "inspection paradox") that suggests a surprising thing, the average time between discrete events observed by many other randomly interspersed observation is the same as the average time between events. This should be surprising, because it suggests that many more people experience times LONGER than the average wait to balance out people who experience a shorter wait because they were randomly closer to the event.
This happens because longer intervals, when they do exist, get a larger proportion of the random observations than the shorter intervals between events. More precisely and generically, when the quantity being observed affects the observer, the observations will be distorted by the quantity in question and have to be normalized.
In the case of a green wave, those rare times when someone waits too long on a green because they were looking at their phone or a naked person in a nearby window and create slowdowns and stalls in the pipeline? Those moments are longer, so there is a longer window where you could experience them.
This actually comes up a lot in observations of things in nature. Imagine if instead of light flips or bus arrivals we were observing clicks on a geiger counter and you'll realize how fundamental this is to experiencing the world.
tl;dr: The Green Wave only works in averages and it can be correct even as your experience of it never working is also correct. :)
[0]: https://jakevdp.github.io/blog/2018/09/13/waiting-time-parad...
Imagine a translucent checker board of blue and black squares overlaid on top of a map of roads. You can see the color of the squares but you can also see the roads underneath them.
If a light falls on a blue square, make its principal north/south direction Red at t=0 and it’s principal east/west direction green. For black squares do the opposite.
Calculate how long it would take cars to travel through each square unimpeded. It helps if this time is roughly the same regardless of direction. It also helps if the squares are roughly large enough that it takes roughly one light phase worth of time to travel through it. Call that time N.
At t=N, switch the lights to the opposite color, so now north/southbound traffic gets green lights on blue squares, and east/west gets red. Black squares get the opposite.
What you’ll see if you imagine yourself as a car driving through this, is that as you enter a black square as it turns green, the green lights will stay green until you enter the nearest blue square, at which point the phase shifts and now the blue square gets green lights as you drive through it.
This works in both directions, with both north and south bound traffic.
Now, this falls apart when roads are diagonal, although it’s mitigated if diagonal roads have higher speed limits. It also means if you make a turn, you’re in the wrong light cycle, but it will correct itself after the next red light.
It also doesn’t work well at all if you have left turn arrows complicating your light cycles, so you’ll need to have something like a Michigan Left and the requisite wide medians to make this work.
It’s not as ideal as I described in real life, but the basic principle works.
It was one my little pleasures to drive along that at 35 (or 30, I forget what the speed limit was) and see cars zip ahead at each light at 45, only to watch them ride the brakes as they hit the next red.
Here it's street view of the road, it's not perfectly visible, because those are LED displays: https://goo.gl/maps/T7RzQCUS4nk3QVZN9
The latter is the colloquial definition.
You’re not wrong that not all race conditions are harmful, but i feel you’re doing a pretty bad job of explaining what a race condition is.
That's literally how 'race condition' is defined in the industry.
You can use it to refer to any race, but in reality the race you are referring to is only subjective to the user. The server is generally processing all requests in the order they are received.
A race condition usually is referring to a multithreaded system where the threads interfere with each other, causing undesired effects.
Relaxing "race condition" to refer to all causality... is such a loose definition as to give it no meaning at all.
I did a PhD partially on irregular parallelism where understanding race conditions are essential for understanding the topic.
> A race condition or race hazard is the condition of an electronics, software, or other system where the system's substantive behavior is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when one or more of the possible behaviors is undesirable.
Or read the canonical Encyclopaedia of Parallel Computing page 1693
> [A race condition is when there is] some order among events [where the] order is not determined by the program
This is a very dangerous way to understand semantics of programming languages. Programming languages are usually defined via some sort of operational semantics on an abstract machine which occasionally bears some resemblance to how processor semantics are defined. But sometimes there is a massive disconnect between processor semantics and language semantics--and perhaps nowhere is that disconnect greater than memory.
In hardware, there is a firm distinction between memory and registers. Any language which approximates hardware semantics (which includes any mid-level or low-level compiler IR) is going to mirror this distinction with the notion of a potentially infinite register set (if pre-register allocation) and use of explicit instructions to move values to and from memory. But in every high-level language, there is no concept of a memory/register distinction: just values floating around in a giant bag of them, often no notion of loads or stores. You can approximate this by annotating values (as C does, with the volatile specifier), but the resulting semantics end up being muddy because you're relying on an imperfect mapping to get to the intended semantics (don't delete this load/store). Optimization that screws up the mapping of the language memory model to the hardware memory model is anticipated, even 30 years ago--that's why C has a volatile keyword.
It is possible, if you are very careful, to write C code that exposes the underlying hardware model that allows you to reason about your program using that model. But this is not typical C code, and instead requires modifying your code in such a way that you make the intended loads and stores explicit. And if you write your code in the typical way and expect it to map cleanly to hardware semantics, you're going to have a bad time.
Data races are one of those cases where "anything may go" undefined behavior is actually quite necessary. Actual hardware memory models (particularly on "weak" architectures like ARM or PowerPC, or especially the infamously weak Alpha) result in cases where the order of memory traffic is not a consistent view between different threads. Formally specifying the hardware memory model is actually surprisingly difficult, made all the more difficult when you want to give a hardware-agnostic definition [1]. And should you want to introduce hardware transactional memory, break down in despair at the complexity [2].
The great breakthrough in memory models is the definition of the "data-race-free" model. This model says that, if memory accesses are properly protected with locks (i.e., there are no "data races" [3]), you cannot observe a violation of sequential consistency, which makes reasoning much easier. Java used this model for its corrected memory model in Java 5, and then C++11 adapted Java's memory model, and C11 took C++11's model verbatim. The upshot of this approach is that, if you make data races undefined behavior, you don't have to try to figure out how typical memory optimizations, both by the compiler and by the physical hardware, affect visible memory. Trying to work out the effects of these optimizations on the memory system is extraordinarily difficult, and the benefits of doing so are manifestly unclear, since most programmers will need to properly synchronize their code anyways.
[1] The C++ memory model attempts to do this with atomic memory references. The resulting specification (in C++11) is generally considered to be wrong, and the question of how to fix it is still up in the air as of right now.
[2] PLDI 2018 had the first formal semantics that combined transactional memory and weak memory semantics, and went on to prove that ARM's hardware lock elision was broken. This should be showing just how difficult this is to grasp even for theoreticians pushing the boundary, let alone a language trying to make it accessible to typical programmers.
[3] There are two definitions of "data race" going on here. Vernacular definitions usually define it as accesses not protected by synchronization, which permit "benign" data races for regular loads/stores. C/C++ tweaks the definition so as to use it to proscribe undefined behavior, so "benign" data race is oxymoronic in those languages. However, the use of memory_order_relaxed is intended to indicate a permitted data race in the first sense but not the second sense.
Could you or someone else please give a minimal example of an undefined access in C, just so that I can be sure of whether we're talking about the same phenomena here?
#include <threads.h>
int x;
int do_thread(void *) {
for (int i = 0; i < 10000; i++)
x = i;
return 0;
}
int main() {
thrd_t t;
thrd_create(&t, do_thread, NULL);
do_thread(NULL);
thrd_join(&t, NULL);
return 0;
}
There is an undefined data race on x, since it is simultaneously accessed from two threads without any synchronization. Replace the declaration of x with `_Atomic int x;`, and the resulting code is well-defined.I use the term 'race condition' about once a week to point out something may happen before or after something else, because the things are happening in parallel.
Simple example:
me: I just uploaded the file to our shared dropbox
you, refreshing: I don't see it yet
me: race condition
This just isn’t true.
Here’s a concrete example - many JIT compilers use counters to work out when to compile a method. They allow data races in updates to the counters because performance is more important than correctness for them and the impact of a lost write is basically zero. It’s only a bug if you decide it’s a bug.
[1] these sort of assumptions of course tend not to age well.
I guess the real issue here is that 'fast' is ambiguous. In terms of racing it can be used for both lap-times or velocity. lap-times and maximum velocity are not necessarily correlated.
</nerdsnipe>
Just to be pedantic... in some contexts better brakes actually allow you to go faster. Consider racing on an oval track... a car with better braking ability can maintain speed longer as it approaches a corner, then scrub off speed more quickly to navigate the corner. A car with inferior brakes has to start slowing down sooner or risk crashing by taking the corner too fast. So better brakes can lead directly to faster lap times.
Of course both things could be true...
No, you have it the other way around. Rubber brake pads on wheel rims fade with heat; disc brakes are able to dissipate far more heat, and are basically essential for long descents.
The only valid argument against disc brakes on bikes is that they weigh a little more (maybe 1 pound). I suppose you could also argue that brake fluid is more trouble to deal with than a cable, and certainly not as easy to jerry-rig, but hundreds of millions of cars use hydraulic brakes without any trouble these days, and I wouldn't want to have jerry-rigged brakes anyway.
Some people argue that the Earth is flat. That doesn't mean they have a good point.
The morons you're referencing probably think cars should all go back to drum brakes and bias-ply tires so people think more about brake fade and tire grip. It's an idiotic argument. Disc brakes on bikes are better in every way, except weight (they add about 1 pound, maybe). Reducing performance available to a cyclist doesn't make any sense at all; you never know when you're going to need to stop suddenly in the real world.
Some drivers have complained about rain because the tears have been literally pulled out of them under the deceleration
Also, I drift corners instead of braking, it seems to be a lot faster if you nail the tire orientation on the exit.
Fair point. As the old saying goes "looser is faster". The risk, of course, is that you wind up in the wall with your velocity = 0. :-)
Yes, and in other contexts, better brakes make you go slower. Better brakes means bigger brakes: larger rotors (discs), larger and heavier calipers, etc. Larger brake rotors take more energy to accelerate (they're basically a bigger flywheel), so they decrease the car's acceleration. So if your goal is to have a car that accelerates as fast as possible, better brakes are actually a big detriment.
For example, from dvyukov's KTSAN wiki [1]:
Given a sufficiently expressive atomic API and a good
implementation, you pay only for what you really need (if
you pay just a bit less, generated code becomes incorrect).
So performance is not an argument here.
[1] https://github.com/google/ktsan/wiki/READ_ONCE-and-WRITE_ONC...The idea is that if you omit READ_ONCE/WRITE_ONCE when they are needed, it doesn't matter if the code is faster because it is wrong.
Will the fixes consist of adding more synchronization mechanisms (mutexes, semaphores, etc) to prevent potential race conditions?
It's the price to be paid for security. No more branch prediction, no more cutting corners with UB. It's time to do things the right way once and for all and that means setting back performance a good 10-20 years.
I think my follow on comment is to just suggest that they fork it and maintain the project themselves.
EDIT: It was meant as a joke as to how media manages to make something that's good into a problem.
As long as they are done locally and not sending source code to Google.
It's a team fault, meant that maybe the PO didnt spec correctly, then the developer implemented something wrong, then this was missed in the code review, then missed in team testing, then missed in testing before going live. Missed in the PO sign off before live...
If anything I would hope that stuff like this by Google helps to encourage orgs to build better processes. If you succeed as a team, you fail as a team. No scapegoats!
Yes, and in my physics classes I learned a lot about spherical, frictionless cows in a perfect vacuum.
In the real world, the shape of cows is not a sphere, nor even a closed-form equation. And real organization care about blame, and as the saying goes, shit rolls downhill.
I've shipped more than my share of bugs live. I've never felt like I didn't have a team behind me. Obviously I shipped a lot of things that work, as well.
Working someplace humane and rational doesn't just happen, it requires work.
I expect anyone with "Senior" in their job title to do that work, both in terms of setting team culture and establishing post-mortem policies with management.
The difference is that in the real world, organizations exist that don't merely blame the developer, and aren't really that rare. To give you an idea, I simply don't work for managers that are focused on blame. I probe this during any interview I have, or for any position I'm considering. It's on my list of "Life is too short to put up with this."
I don't have trouble finding jobs.
“Google is uncovering millions of gender preference in literature...” /sarcasm
It would be nice to see an effort to migrate some of the kernel core, but I can't see Rust gaining widespread acceptance in the kernel development community any time soon.
I don't even think that a migration effort from C -> Rust for the Linux Kernel is even feasible or even worth it, given the scale of the project. At this point, you might as well start from scratch.
Google is already experimenting with Rust for OS development with their new Fuchsia operating system [0], which has some drivers written in Rust with a capability based security model.
Similarly, Firefox's Project Quantum seeks to bring Rust to Firefox from the more experimental Servo project.
There are 2 reasons the Linux kernel in particular uses C and will continue to use C: 1) it was C originally and 2) it should compile without requiring some other compiler.
Anyone who sets out to write a new OS kernel (or similar) today for platforms where Rust is available, would probably/hopefully use Rust over C. That doesn't mean it's a good idea to start making existing code bases "hybrids". No one wants a huge codebase where to build it you need N compilers for languages that were popular in various times during the code bases hisory, and where you need to be an expert in N languages to maintain it.
And IMHO, such large number of patches / fixes on Linux should always use a decent open audit process.
The tool is released as open source.
> such large number of patches / fixes on Linux should always use a decent open audit process
All contributions to the Linux kernel are audited by the kernel development team. Look at kernel.org, you'll see that contributions are always signed off by at least one other kernel dev, and often also Linus himself.
> For those wanting to learn more, the code at least for now is being hosted on GitHub.
Google basically announced their project and published the code. That's it. In their email to the LKML (https://lkml.org/lkml/2019/9/20/394) they specifically mention:
In the coming weeks we're planning to:
* Attempt to send fixes for some races upstream […]
There are a few open questions:
* How/when to upstream KCSAN?
So they intend to follow the standard contribution process of sending fixes upstream.How is this a sufficient point of confusion to lead to this comment chain? It’s a link. Click on it and see.