diff options
author | Johannes Weiner <hannes@cmpxchg.org> | 2009-02-04 18:12:14 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-02-05 15:56:48 -0500 |
commit | 777c6c5f1f6e757ae49ecca2ed72d6b1f523c007 (patch) | |
tree | 342b79faee43af9705b5a8ca406565fda0ad08fd /kernel/sched.c | |
parent | 40b0bb1e734700b81d2ec69367c035cd1537f4fa (diff) |
wait: prevent exclusive waiter starvation
With exclusive waiters, every process woken up through the wait queue must
ensure that the next waiter down the line is woken when it has finished.
Interruptible waiters don't do that when aborting due to a signal. And if
an aborting waiter is concurrently woken up through the waitqueue, noone
will ever wake up the next waiter.
This has been observed with __wait_on_bit_lock() used by
lock_page_killable(): the first contender on the queue was aborting when
the actual lock holder woke it up concurrently. The aborted contender
didn't acquire the lock and therefor never did an unlock followed by
waking up the next waiter.
Add abort_exclusive_wait() which removes the process' wait descriptor from
the waitqueue, iff still queued, or wakes up the next waiter otherwise.
It does so under the waitqueue lock. Racing with a wake up means the
aborting process is either already woken (removed from the queue) and will
wake up the next waiter, or it will remove itself from the queue and the
concurrent wake up will apply to the next waiter after it.
Use abort_exclusive_wait() in __wait_event_interruptible_exclusive() and
__wait_on_bit_lock() when they were interrupted by other means than a wake
up through the queue.
[akpm@linux-foundation.org: coding-style fixes]
Reported-by: Chris Mason <chris.mason@oracle.com>
Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
Mentored-by: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Matthew Wilcox <matthew@wil.cx>
Cc: Chuck Lever <cel@citi.umich.edu>
Cc: Nick Piggin <nickpiggin@yahoo.com.au>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: <stable@kernel.org> ["after some testing"]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel/sched.c')
-rw-r--r-- | kernel/sched.c | 4 |
1 files changed, 2 insertions, 2 deletions
diff --git a/kernel/sched.c b/kernel/sched.c index 242d0d47a70d..8ee437a5ec1d 100644 --- a/kernel/sched.c +++ b/kernel/sched.c | |||
@@ -4697,8 +4697,8 @@ EXPORT_SYMBOL(default_wake_function); | |||
4697 | * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns | 4697 | * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns |
4698 | * zero in this (rare) case, and we handle it by continuing to scan the queue. | 4698 | * zero in this (rare) case, and we handle it by continuing to scan the queue. |
4699 | */ | 4699 | */ |
4700 | static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, | 4700 | void __wake_up_common(wait_queue_head_t *q, unsigned int mode, |
4701 | int nr_exclusive, int sync, void *key) | 4701 | int nr_exclusive, int sync, void *key) |
4702 | { | 4702 | { |
4703 | wait_queue_t *curr, *next; | 4703 | wait_queue_t *curr, *next; |
4704 | 4704 | ||