<html>
<head>
<base href="https://llvm.org/bugs/" />
</head>
<body><table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Bug ID</th>
<td><a class="bz_bug_link
bz_status_NEW " title="NEW --- - TSAN incorrectly model condition variable semantics" href="https://urldefense.proofpoint.com/v2/url?u=https-3A__llvm.org_bugs_show-5Fbug.cgi-3Fid-3D23616&d=AwMBaQ&c=8hUWFZcy2Z-Za5rBPlktOQ&r=pF93YEPyB-J_PERP4DUZOJDzFVX5ZQ57vQk33wu0vio&m=9afZ1te3ExIWdWQah38Mm66-pMpj2tAAvCCWMKSTmI8&s=jBsGVmUT081_hslAbeWfnCbBy7OTp3lKv9Mg4ACo_g8&e=">23616</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>TSAN incorrectly model condition variable semantics
</td>
</tr>
<tr>
<th>Product</th>
<td>compiler-rt
</td>
</tr>
<tr>
<th>Version</th>
<td>unspecified
</td>
</tr>
<tr>
<th>Hardware</th>
<td>PC
</td>
</tr>
<tr>
<th>OS</th>
<td>Linux
</td>
</tr>
<tr>
<th>Status</th>
<td>NEW
</td>
</tr>
<tr>
<th>Severity</th>
<td>normal
</td>
</tr>
<tr>
<th>Priority</th>
<td>P
</td>
</tr>
<tr>
<th>Component</th>
<td>compiler-rt
</td>
</tr>
<tr>
<th>Assignee</th>
<td>unassignedbugs@nondot.org
</td>
</tr>
<tr>
<th>Reporter</th>
<td>eric@efcs.ca
</td>
</tr>
<tr>
<th>CC</th>
<td>llvmbugs@cs.uiuc.edu
</td>
</tr>
<tr>
<th>Classification</th>
<td>Unclassified
</td>
</tr></table>
<p>
<div>
<pre>The example execution should be well formed but it currently causes TSAN to
incorrectly report a data race.
Step Thread A Thread B
1. ... lk.lock()
2. ... cv.wait(lk)
3. lk.lock() ...
4. cv.notify_one() ...
5. cv.~condition_variable() ...
6. lk.unlock() ...
7. ... finally exits cv.wait // ok, not a data race
For this execution TSAN will report something akin to:
1. In step 5 thread A writes to cv while holding lk.
2. Previous read of cv by thread B in step 2.
Note that when TSAN intercepts cv.wait(lk) it unlocks lk before annotating the
read. This is what leads to the incorrect behaviour.
To restate C++14 [thread.condition]p3 the call to `wait(lock)` has three atomic
steps (denoted Wait.X)
Wait.1: Enter the waiting queue and release the lock.
Wait.2: Become unblocked in wait().
Wait.3: Reacquire the mutex.
In order to show that there is no data race I will demonstrate that Thread A
cannot reach step 5 until thread B completes Wait.2. This condition is
sufficient because once thread B completes Wait.2 the condition variable can be
safely destroyed according to the wording in 30.5.1 p5.
Obviously thread A cannot proceed past step 3 until thread B completes Wait.1
and releases the mutex.
Thread B cannot complete Wait.2 until it is notified by thread A. The effects
of notify_all() say that the unblocking of thread B "happens before" thread A
returns from the notify_all().
Therefore thread B must reach Wait.3 before thread A can reach step 5
Below are the relevant clauses from the C++14 standard regarding condition
variable.
30.5 [thread.condition]p3:
<span class="quote">>p3 The execution of notify_one and notify_all shall be atomic. The execution
> of wait, wait_for, and
> wait_until shall be performed in three atomic parts:
> 1. the release of the mutex and entry into the waiting state;
> 2. the unblocking of the wait; and
> 3. the reacquisition of the lock.
>p4 The implementation shall behave as if all executions of notify_one,
> notify_all, and each part of the wait,
> wait_for, and wait_until executions are executed in a single unspecified
> total order consistent with the "happens before" order.</span >
30.5.1 [thread.condition.condvar]p5
<span class="quote">> ~ condition_variable();
> Requires: There shall be no thread blocked on *this. [ Note: That is, all
> threads shall have been notified; they may subsequently block on the
> lock specified in the wait. This relaxes the usual rules, which would
> have required all wait calls to happen before destruction. Only the
> notification to unblock the wait must happen before destruction. The user
> must take care to ensure that no threads wait on
> *this once the destructor has been started, especially when the waiting
> threads are calling the wait</span >
30.5.1 [thread.condition.condvar]p8
<span class="quote">> void notify_all() noexcept;
> Effects: Unblocks all threads that are blocked waiting for *this.</span >
The following libc++ test serves as a minimal reproducer.
<a href="https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_llvm-2Dmirror_libcxx_blob_master_test_std_thread_thread.condition_thread.condition.condvar_destructor.pass.cpp&d=AwMBaQ&c=8hUWFZcy2Z-Za5rBPlktOQ&r=pF93YEPyB-J_PERP4DUZOJDzFVX5ZQ57vQk33wu0vio&m=9afZ1te3ExIWdWQah38Mm66-pMpj2tAAvCCWMKSTmI8&s=0liP3kJwTTeFkp8JK01Lsasuy4RVHYaKU3ubtfTm_5E&e=">https://github.com/llvm-mirror/libcxx/blob/master/test/std/thread/thread.condition/thread.condition.condvar/destructor.pass.cpp</a></pre>
</div>
</p>
<hr>
<span>You are receiving this mail because:</span>
<ul>
<li>You are on the CC list for the bug.</li>
</ul>
</body>
</html>