aboutsummaryrefslogtreecommitdiffstats
path: root/lib/sbitmap.c
diff options
context:
space:
mode:
authorJens Axboe <axboe@kernel.dk>2018-05-14 14:17:31 -0400
committerJens Axboe <axboe@kernel.dk>2018-05-14 14:17:31 -0400
commitc854ab5773be1c1a0d3cef0c3a3261f2c48ab7f8 (patch)
treef8736038b49c548fd13327a3d2db26804f027047 /lib/sbitmap.c
parent0eb0b63c1d1a851b4c1606f4170691835d3616a2 (diff)
sbitmap: fix race in wait batch accounting
If we have multiple callers of sbq_wake_up(), we can end up in a situation where the wait_cnt will continually go more and more negative. Consider the case where our wake batch is 1, hence wait_cnt will start out as 1. wait_cnt == 1 CPU0 CPU1 atomic_dec_return(), cnt == 0 atomic_dec_return(), cnt == -1 cmpxchg(-1, 0) (succeeds) [wait_cnt now 0] cmpxchg(0, 1) (fails) This ends up with wait_cnt being 0, we'll wakeup immediately next time. Going through the same loop as above again, and we'll have wait_cnt -1. For the case where we have a larger wake batch, the only difference is that the starting point will be higher. We'll still end up with continually smaller batch wakeups, which defeats the purpose of the rolling wakeups. Always reset the wait_cnt to the batch value. Then it doesn't matter who wins the race. But ensure that whomever does win the race is the one that increments the ws index and wakes up our batch count, loser gets to call __sbq_wake_up() again to account his wakeups towards the next active wait state index. Fixes: 6c0ca7ae292a ("sbitmap: fix wakeup hang after sbq resize") Reviewed-by: Omar Sandoval <osandov@fb.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
Diffstat (limited to 'lib/sbitmap.c')
-rw-r--r--lib/sbitmap.c35
1 files changed, 25 insertions, 10 deletions
diff --git a/lib/sbitmap.c b/lib/sbitmap.c
index 8f0950fbaa5c..e6d7d610778d 100644
--- a/lib/sbitmap.c
+++ b/lib/sbitmap.c
@@ -457,7 +457,7 @@ static struct sbq_wait_state *sbq_wake_ptr(struct sbitmap_queue *sbq)
457 return NULL; 457 return NULL;
458} 458}
459 459
460static void sbq_wake_up(struct sbitmap_queue *sbq) 460static bool __sbq_wake_up(struct sbitmap_queue *sbq)
461{ 461{
462 struct sbq_wait_state *ws; 462 struct sbq_wait_state *ws;
463 unsigned int wake_batch; 463 unsigned int wake_batch;
@@ -474,28 +474,43 @@ static void sbq_wake_up(struct sbitmap_queue *sbq)
474 474
475 ws = sbq_wake_ptr(sbq); 475 ws = sbq_wake_ptr(sbq);
476 if (!ws) 476 if (!ws)
477 return; 477 return false;
478 478
479 wait_cnt = atomic_dec_return(&ws->wait_cnt); 479 wait_cnt = atomic_dec_return(&ws->wait_cnt);
480 if (wait_cnt <= 0) { 480 if (wait_cnt <= 0) {
481 int ret;
482
481 wake_batch = READ_ONCE(sbq->wake_batch); 483 wake_batch = READ_ONCE(sbq->wake_batch);
484
482 /* 485 /*
483 * Pairs with the memory barrier in sbitmap_queue_resize() to 486 * Pairs with the memory barrier in sbitmap_queue_resize() to
484 * ensure that we see the batch size update before the wait 487 * ensure that we see the batch size update before the wait
485 * count is reset. 488 * count is reset.
486 */ 489 */
487 smp_mb__before_atomic(); 490 smp_mb__before_atomic();
491
488 /* 492 /*
489 * If there are concurrent callers to sbq_wake_up(), the last 493 * For concurrent callers of this, the one that failed the
490 * one to decrement the wait count below zero will bump it back 494 * atomic_cmpxhcg() race should call this function again
491 * up. If there is a concurrent resize, the count reset will 495 * to wakeup a new batch on a different 'ws'.
492 * either cause the cmpxchg to fail or overwrite after the
493 * cmpxchg.
494 */ 496 */
495 atomic_cmpxchg(&ws->wait_cnt, wait_cnt, wait_cnt + wake_batch); 497 ret = atomic_cmpxchg(&ws->wait_cnt, wait_cnt, wake_batch);
496 sbq_index_atomic_inc(&sbq->wake_index); 498 if (ret == wait_cnt) {
497 wake_up_nr(&ws->wait, wake_batch); 499 sbq_index_atomic_inc(&sbq->wake_index);
500 wake_up_nr(&ws->wait, wake_batch);
501 return false;
502 }
503
504 return true;
498 } 505 }
506
507 return false;
508}
509
510static void sbq_wake_up(struct sbitmap_queue *sbq)
511{
512 while (__sbq_wake_up(sbq))
513 ;
499} 514}
500 515
501void sbitmap_queue_clear(struct sbitmap_queue *sbq, unsigned int nr, 516void sbitmap_queue_clear(struct sbitmap_queue *sbq, unsigned int nr,