News for hackers. Stuff that matters.
Re:How to trigger races reliably in the Linux kernel by adtac on Tuesday May 05, 2020 @09:06AM (#23079590)
is there any situation where a data race might be useful or accommodatable?
Re:How to trigger races reliably in the Linux kernel by chrisseaton on Tuesday May 05, 2020 @09:11AM (#23079622)
For example, some profilers that count the number of times something is run will update a counter without any synchronisation. This risks lost updates, but since you really just need to know if the counter is very low or very high a few lost updates here or there don't really matter.

But I think from some accepted definitions (such as Padua et al) a data race specifically is a bug, so this data interleaving if we're happy with it isn't a data race because it isn't a bug.

Re:How to trigger races reliably in the Linux kernel by robocat on Tuesday May 05, 2020 @05:30PM (#23085304)
> a few lost updates here or there don't really matter

jerf disagrees in last sentence of other comment: https://news.ycombinator.com/item?id=23079788

Re:How to trigger races reliably in the Linux kernel by chrisseaton on Tuesday May 05, 2020 @07:53PM (#23086477)
Well I don't know what to tell you except this is how real production compilers written by experts work in practice.

https://github.com/oracle/graal/blob/4553ea71f15c9e4721565e9...

You can argue this is a bug and they shouldn't write like this, but they don't consider it a bug and they do write like this.

Re:How to trigger races reliably in the Linux kernel by yencabulator on Wednesday May 06, 2020 @07:46PM (#23097882)
Profiling is a tricky edge case though. If gathering the profiling data costs too much, the results are completely useless. Especially relevant is all the quirky side effects like cache pressure.

So your choices here tend to be: sometimes wrong, or completely useless.

Re:How to trigger races reliably in the Linux kernel by jfkebwjsbx on Tuesday May 05, 2020 @10:00AM (#23080072)
+1, at $DAYJOB we use intentional data races all the time for similar purposes.
Re:How to trigger races reliably in the Linux kernel by Unklejoe on Tuesday May 05, 2020 @09:24AM (#23079759)
One time, I wrote a small network protocol stack to handle some proprietary UDP-based protocol that supported fragmentation.

Since the header on each fragment contained the total message size, I was able to pre-allocate the full size of the message upon receipt of the first fragment.

Then, as fragments arrived, I would copy their data into their respective areas of the message.

Typically, everything would arrive in order, so the message would be copied from beginning to end, piece by piece.

However, since this was Ethernet/UDP, it was possible for frames to arrive out of order.

This would result in the message being pieced together, with each fragment filling in its hole until all fragments have been received.

I guess this is a form of a data race? Or rather, a case where a data race at least wasn't detrimental.

Re:How to trigger races reliably in the Linux kernel by Liquid_Fire on Tuesday May 05, 2020 @10:24AM (#23080307)
A data race requires multiple threads to access the same memory at the same time and at least one of them to be writing the data.

There is no data race in your example, since the fragments are all written to different parts of memory, unless you had something else reading the message while the individual fragments were still being written.

Re:How to trigger races reliably in the Linux kernel by Measter on Tuesday May 05, 2020 @09:26AM (#23079783)
I have one example where I didn't see a race as an issue. I was transferring data from one thread, to a second thread which would process it after a delay. This transfer was done by locking a mutex, then pushing it onto a queue.

The second thread does an unsynchronized fetch of the queue length, and then uses that fetched length as a condition in a loop that does a synchronised dequeue of a single item.

In this case, the race condition is not a problem, because the only place that data is removed is in that loop, which is a synchronised access.

So the worse case for a race here is that the length that's been read is less than the actual length, which is not a problem because that new data will just be handled the next time round.

Re:How to trigger races reliably in the Linux kernel by StillBored on Tuesday May 05, 2020 @10:05AM (#23080110)
Well for the readers (not saying you have a bug), what you describe can fail on any processor that doesn't implement TSO. Due to the fact that the write of your "queue length" may be visible before the queued item is fully visible. Meaning you read garbage from the newly queued item. Worse, even on a TSO architecture, unless you have a way to explicitly guarantee a compiler fence or there is an explicit data dependency the compiler is likely free to hoist the code which updates the queue length ahead of the code doing the queue update. Again meaning the unsyncronized thread reads that there is a new queue item before that queue item is visible to the reader.

How critical this ends up being to the program is heavily dependent on the queue data structure and how its being manipulated. But in a simple copy/linked list type situation referencing garbage data is quite likely.

A lot of traditional comp sci programs are plenty happy to teach about data synchronization primitives but completely fail to mention that most general API (posix/etc) primitives also have read/write fences implied. Those fences are as important on any processor made in the last two decades as the atomic operations themselves. As generally the implementations also provide both architectural memory barriers as well as compiler level fencing to assure the compiler isn't reordering the primitive with the code it is intended to protect. Going further, many architectures provide unfenced atomics. So, if you decide to code your own atomic_inc/swap/etc you have to also understand the memory model of the target machine before you have actually created something equivalent to what is taught in your average comp-sci department.

Re:How to trigger races reliably in the Linux kernel by Measter on Tuesday May 05, 2020 @11:21AM (#23080956)
The code in question basically looks like this(C#):

    // Called from Thread 1.
    public void AddData(Data theData) {
        lock(dataQueue) {
            dataQueue.Enqueue(theData);
        }
    }

    // Worker for Thread 2.
    private void ProcessingThread() {
        while(true) {
            int length = dataQueue.Count;
            for (int i = 0; i < length; i++) {
                Data curData;
                lock(dataQueue) {
                    curData = dataQueue.Dequeue();
                }

                // Process data.
            }
   
        // Sleep for a short while.
        }
    }
And all of that is in its own class, where these are the only two functions that access the data queue.

So, from what I can see, in the event where the length is updated before the data is actually inserted, Thread 2 is still forced to wait until Thread 1 is finished before it can access it so it's still safe. Though if I'm wrong, please tell me. No one wants garbage data.

Re:How to trigger races reliably in the Linux kernel by gpderetta on Tuesday May 05, 2020 @12:26PM (#23081765)
I don't claim to know the c# memory model, but there is a chance that the compiler could hoist the 'int lenght = dataQueue.Count out of the loop.

Also, it is possible that Count is not a member but an accessor and it might return garbage or crash if it sees some inconsistent data.

Still, both of the above can be rare occurrences. The reason that you are not seeing no particular issues is because you are basically busy waiting and polling the queue (i.e. the sleep comment); if you replaced the dataQueue.Count with just a random number generator, the code will still work. If you were doing proper signaling instead of sleeping, then your code will get stuck in case of a missed wakeup.

Re:How to trigger races reliably in the Linux kernel by Measter on Tuesday May 05, 2020 @01:48PM (#23082700)
I'm not sure if it would be allowed to hoist `dataQueue.Count` out of the loop. The docs [0] say that multiple concurrent readers are supported, so I'm not sure if the compiler would be allowed to assume anything about the call's return value.

You are correct in that `Count` is a property, and it could be doing extra things, but according to the implementation [1] it just returns the value of a private integer. Of course, this is an implementation detail and shouldn't be relied upon, but I'm not sure how much that could be changed.

However, you are mistaken about the code continuing to work if `dataQueue.Count` were replaced with an RNG, or if it were otherwise higher than it should be. The `Dequeue` function raises an exception if called on an empty queue. This one is part of the API, not an implementation detail. An uncaught exception here would crash the thread, not just blindly carry on.

Of course, I'm no expert, and could just be talking out of my ass.

[0] https://docs.microsoft.com/en-gb/dotnet/api/system.collectio... [1] https://github.com/dotnet/runtime/blob/master/src/libraries/...

Re:How to trigger races reliably in the Linux kernel by gpderetta on Wednesday May 06, 2020 @06:41AM (#23089525)
Concurrent readers are supported, but not concurrent writers and readers. As Count is getting called outside of the critical section, the compiler can assume that there are no concurrent writers. Admittedly, the following critical section would make it hard for the compiler to actually apply any optimization in practice.

Count is implemented as a simple read currently, but that's not guaranteed to be the case in the future (that's the whole reason for having a property.

Fair enough about the rng, I'm not really a c# programmer, so I just assumed that dequeue would return a null on an empty queue.

I'm not an expert either, and certainly not a c# expert (although I kind of like the language, I haven't written a line in 7 years), so YMMV.

Re:How to trigger races reliably in the Linux kernel by CodesInChaos on Tuesday May 05, 2020 @11:29AM (#23081083)
Data races on normal memory access are undefined behaviour in many languages (e.g. C, C++, Rust), so I assume that you mean races using atomic loads with relaxed ordering.

One practical example of using those is Hogwild, which is an implementation of stochastic gradient descent which skips synchronization on individual parameter updates, assuming that it'll converge even if some writes are lost or some reads are stale.

https://papers.nips.cc/paper/4390-hogwild-a-lock-free-approa...

Re:How to trigger races reliably in the Linux kernel by bflesch on Tuesday May 05, 2020 @09:08AM (#23079604)
Querying multiple backends and responding with the data that has been received first. Not sure if this technique is really in use, but I figure this could be an optimization for dns queries and the like.
Re:How to trigger races reliably in the Linux kernel by Znafon on Tuesday May 05, 2020 @09:14AM (#23079649)
This is exactly how the "Happy Eyeballs" algorithm in RFC 8305 works: https://en.wikipedia.org/wiki/Happy_Eyeballs
Re:How to trigger races reliably in the Linux kernel by PudgePacket on Tuesday May 05, 2020 @09:52AM (#23080011)
Definitely in use, I know at least Google does this internally.
Re:How to trigger races reliably in the Linux kernel by abtinf on Tuesday May 05, 2020 @09:27AM (#23079790)
Any system where the only useful data is whatever was last written.

In certain kinds of signals intelligence software, the primary concern is access to the current frame of data. You don’t want to miss an RF peak that lasts just a few nanoseconds. It can be preferable to have half the the frame overwritten mid-processing every few million iterations than to slow down each iteration and force a lower overall refresh rate.

Re:How to trigger races reliably in the Linux kernel by PeterisP on Tuesday May 05, 2020 @09:14AM (#23079654)
Let's say you have query that returns a bunch of rows from a sharded database by asking every shard and naively combining their responses by simply forwarding all rows as soon as you receive them.

That's a data race, the result will vary between executions because the row order will be different depending on how fast the various servers respond, but acknowledging that the order is not-well defined or guaranteed makes this data race accomodatable and even useful, since that gives a performance benefit compared to any solution that needs to ensure a specific order of these rows.

Re:How to trigger races reliably in the Linux kernel by zozbot234 on Tuesday May 05, 2020 @09:29AM (#23079801)
This is not a "data race" in a formal sense. A data race involves multiple threads accessing a non-atomic data object in ways that might result in an unintended and inconsistent state if the accesses overlap. That's always a bug.
Re:How to trigger races reliably in the Linux kernel by jfkebwjsbx on Tuesday May 05, 2020 @09:58AM (#23080055)
A data race does not necessarily imply a bug, even within your definition (which is only relevant for classical multithreaded applications).
Re:How to trigger races reliably in the Linux kernel by steerablesafe on Tuesday May 05, 2020 @11:11AM (#23080855)
In C++ a data race is UB, so it is most definitely always a bug there.
Re:How to trigger races reliably in the Linux kernel by CodesInChaos on Tuesday May 05, 2020 @12:29PM (#23081791)
The C++ standard's definition of a datarace only applies to ordinary memory access and rules those cases UB. But if you port such code to use relaxed load/store operations it's just as racey, but no longer UB. I'd consider still consider those cases data races, even though they don't meet the C++ standard's definition of them.
Re:How to trigger races reliably in the Linux kernel by steerablesafe on Thursday May 07, 2020 @08:30AM (#23102200)
Then you probably don't consider Rust datarace free as well.
Re:How to trigger races reliably in the Linux kernel by threatofrain on Tuesday May 05, 2020 @12:07PM (#23081549)
IMO most front end JS apps have to deal with data races over the wire with multiple API calls, so it’s more like a natural condition of driving.