summaryrefslogtreecommitdiff
path: root/kernel/workqueue.c
diff options
context:
space:
mode:
authorPavankumar Kondeti <pkondeti@codeaurora.org>2017-01-04 10:41:15 +0530
committerPavankumar Kondeti <pkondeti@codeaurora.org>2017-01-11 11:53:39 +0530
commitd1f21a7e9c9ac92a7b6bf11874af6b421f144d92 (patch)
tree7145f5ed3326f5ee73066702a4fa7ee7ed6fc67f /kernel/workqueue.c
parent0a8e939a4e57d86da88c9856cd266447fc45ea85 (diff)
workqueue: fix possible livelock with concurrent mod_delayed_work()
When mod_delayed_work() is concurrently executed, there a potential live lock scenario due to pool->lock contention. Lets say both CPU#0 and CPU#4 calls mod_delayed_work() on the same work item with 0 delay on a bounded workqueue. This workitem has run on CPU#4 previously. CPU#0 wins the work item PENDING bit race and proceeds to queueing. As this work has previously run on CPU#4, it tries to acquire the corresponding pool->lock to check if it is still running there. In the meantime, CPU#4 loops in try_to_grab_pending() for the workitem to be linked with a pwq so that it can steal it from pwq->pool->worklist. The CPU#4 essentially acquires and releases the pool->lock in a busy loop and CPU#0 may never gets this lock. ---------------- -------------------- CPU#0 CPU#4 --------------- -------------------- blk_run_queue_async() mod_delayed_work_on() queue_unplugged() --> try_to_grab_pending() returns blk_run_queue_async() 0 indicating PENDING bit is set now. __queue_delayed_work() mod_delayed_work_on() __queue_work() try_to_grab_pending() { --> waiting for the CPU#4's acquire pool->lock() pool->lock release pool->lock() } Change-Id: I9aeab111f55a19478a9d045c8e3576bce3b7a7c5 Signed-off-by: Pavankumar Kondeti <pkondeti@codeaurora.org>
Diffstat (limited to 'kernel/workqueue.c')
-rw-r--r--kernel/workqueue.c7
1 files changed, 7 insertions, 0 deletions
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 316b316c7528..c0ab232e3abd 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -49,6 +49,7 @@
#include <linux/moduleparam.h>
#include <linux/uaccess.h>
#include <linux/bug.h>
+#include <linux/delay.h>
#include "workqueue_internal.h"
@@ -1285,6 +1286,12 @@ fail:
if (work_is_canceling(work))
return -ENOENT;
cpu_relax();
+ /*
+ * The queueing is in progress in another context. If we keep
+ * taking the pool->lock in a busy loop, the other context may
+ * never get the lock. Give 1 usec delay to avoid this contention.
+ */
+ udelay(1);
return -EAGAIN;
}