aboutsummaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-04-08 18:30:07 -0400
committerNitin Garg <nitin.garg@freescale.com>2014-04-17 22:54:51 -0400
commit2d35df474ac2df49602efe03bcaa2b0a2b1d9a38 (patch)
tree950281fd5f533f5b71ae05126328fa34ea109eea /kernel
parentda12dd93466be7d247f28a6f51872ccbe122c919 (diff)
futex: avoid race between requeue and wake
Jan Stancek reported: "pthread_cond_broadcast/4-1.c testcase from openposix testsuite (LTP) occasionally fails, because some threads fail to wake up. Testcase creates 5 threads, which are all waiting on same condition. Main thread then calls pthread_cond_broadcast() without holding mutex, which calls: futex(uaddr1, FUTEX_CMP_REQUEUE_PRIVATE, 1, 2147483647, uaddr2, ..) This immediately wakes up single thread A, which unlocks mutex and tries to wake up another thread: futex(uaddr2, FUTEX_WAKE_PRIVATE, 1) If thread A manages to call futex_wake() before any waiters are requeued for uaddr2, no other thread is woken up" The ordering constraints for the hash bucket waiter counting are that the waiter counts have to be incremented _before_ getting the spinlock (because the spinlock acts as part of the memory barrier), but the "requeue" operation didn't honor those rules, and nobody had even thought about that case. This fairly simple patch just increments the waiter count for the target hash bucket (hb2) when requeing a futex before taking the locks. It then decrements them again after releasing the lock - the code that actually moves the futex(es) between hash buckets will do the additional required waiter count housekeeping. Reported-and-tested-by: Jan Stancek <jstancek@redhat.com> Acked-by: Davidlohr Bueso <davidlohr@hp.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: stable@vger.kernel.org # 3.14 Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel')
-rw-r--r--kernel/futex.c5
1 files changed, 5 insertions, 0 deletions
diff --git a/kernel/futex.c b/kernel/futex.c
index 43cb88ddae79..4feeda77286e 100644
--- a/kernel/futex.c
+++ b/kernel/futex.c
@@ -1452,6 +1452,7 @@ retry:
1452 hb2 = hash_futex(&key2); 1452 hb2 = hash_futex(&key2);
1453 1453
1454retry_private: 1454retry_private:
1455 hb_waiters_inc(hb2);
1455 double_lock_hb(hb1, hb2); 1456 double_lock_hb(hb1, hb2);
1456 1457
1457 if (likely(cmpval != NULL)) { 1458 if (likely(cmpval != NULL)) {
@@ -1461,6 +1462,7 @@ retry_private:
1461 1462
1462 if (unlikely(ret)) { 1463 if (unlikely(ret)) {
1463 double_unlock_hb(hb1, hb2); 1464 double_unlock_hb(hb1, hb2);
1465 hb_waiters_dec(hb2);
1464 1466
1465 ret = get_user(curval, uaddr1); 1467 ret = get_user(curval, uaddr1);
1466 if (ret) 1468 if (ret)
@@ -1510,6 +1512,7 @@ retry_private:
1510 break; 1512 break;
1511 case -EFAULT: 1513 case -EFAULT:
1512 double_unlock_hb(hb1, hb2); 1514 double_unlock_hb(hb1, hb2);
1515 hb_waiters_dec(hb2);
1513 put_futex_key(&key2); 1516 put_futex_key(&key2);
1514 put_futex_key(&key1); 1517 put_futex_key(&key1);
1515 ret = fault_in_user_writeable(uaddr2); 1518 ret = fault_in_user_writeable(uaddr2);
@@ -1519,6 +1522,7 @@ retry_private:
1519 case -EAGAIN: 1522 case -EAGAIN:
1520 /* The owner was exiting, try again. */ 1523 /* The owner was exiting, try again. */
1521 double_unlock_hb(hb1, hb2); 1524 double_unlock_hb(hb1, hb2);
1525 hb_waiters_dec(hb2);
1522 put_futex_key(&key2); 1526 put_futex_key(&key2);
1523 put_futex_key(&key1); 1527 put_futex_key(&key1);
1524 cond_resched(); 1528 cond_resched();
@@ -1594,6 +1598,7 @@ retry_private:
1594 1598
1595out_unlock: 1599out_unlock:
1596 double_unlock_hb(hb1, hb2); 1600 double_unlock_hb(hb1, hb2);
1601 hb_waiters_dec(hb2);
1597 1602
1598 /* 1603 /*
1599 * drop_futex_key_refs() must be called outside the spinlocks. During 1604 * drop_futex_key_refs() must be called outside the spinlocks. During