Skip to content

Commit

Permalink
block: exit_io_context() should call elevator_exit_icq_fn()
Browse files Browse the repository at this point in the history
While updating locking, b2efa05 "block, cfq: unlink
cfq_io_context's immediately" moved elevator_exit_icq_fn() invocation
from exit_io_context() to the final ioc put.  While this doesn't cause
catastrophic failure, it effectively removes task exit notification to
elevator and cause noticeable IO performance degradation with CFQ.

On task exit, CFQ used to immediately expire the slice if it was being
used by the exiting task as no more IO would be issued by the task;
however, after b2efa05, the notification is lost and disk could sit
idle needlessly, leading to noticeable IO performance degradation for
certain workloads.

This patch renames ioc_exit_icq() to ioc_destroy_icq(), separates
elevator_exit_icq_fn() invocation into ioc_exit_icq() and invokes it
from exit_io_context().  ICQ_EXITED flag is added to avoid invoking
the callback more than once for the same icq.

Walking icq_list from ioc side and invoking elevator callback requires
reverse double locking.  This may be better implemented using RCU;
unfortunately, using RCU isn't trivial.  e.g. RCU protection would
need to cover request_queue and queue_lock switch on cleanup makes
grabbing queue_lock from RCU unsafe.  Reverse double locking should
do, at least for now.

Signed-off-by: Tejun Heo <tj@kernel.org>
Reported-and-bisected-by: Shaohua Li <shli@kernel.org>
LKML-Reference: <CANejiEVzs=pUhQSTvUppkDcc2TNZyfohBRLygW5zFmXyk5A-xQ@mail.gmail.com>
Tested-by: Shaohua Li <shaohua.li@intel.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
  • Loading branch information
Tejun Heo authored and Jens Axboe committed Feb 15, 2012
1 parent 2274b02 commit 621032a
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 8 deletions.
55 changes: 47 additions & 8 deletions block/blk-ioc.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,22 @@ static void icq_free_icq_rcu(struct rcu_head *head)
kmem_cache_free(icq->__rcu_icq_cache, icq);
}

/*
* Exit and free an icq. Called with both ioc and q locked.
*/
/* Exit an icq. Called with both ioc and q locked. */
static void ioc_exit_icq(struct io_cq *icq)
{
struct elevator_type *et = icq->q->elevator->type;

if (icq->flags & ICQ_EXITED)
return;

if (et->ops.elevator_exit_icq_fn)
et->ops.elevator_exit_icq_fn(icq);

icq->flags |= ICQ_EXITED;
}

/* Release an icq. Called with both ioc and q locked. */
static void ioc_destroy_icq(struct io_cq *icq)
{
struct io_context *ioc = icq->ioc;
struct request_queue *q = icq->q;
Expand All @@ -60,8 +72,7 @@ static void ioc_exit_icq(struct io_cq *icq)
if (rcu_dereference_raw(ioc->icq_hint) == icq)
rcu_assign_pointer(ioc->icq_hint, NULL);

if (et->ops.elevator_exit_icq_fn)
et->ops.elevator_exit_icq_fn(icq);
ioc_exit_icq(icq);

/*
* @icq->q might have gone away by the time RCU callback runs
Expand Down Expand Up @@ -95,7 +106,7 @@ static void ioc_release_fn(struct work_struct *work)
struct request_queue *q = icq->q;

if (spin_trylock(q->queue_lock)) {
ioc_exit_icq(icq);
ioc_destroy_icq(icq);
spin_unlock(q->queue_lock);
} else {
spin_unlock_irqrestore(&ioc->lock, flags);
Expand Down Expand Up @@ -142,13 +153,41 @@ EXPORT_SYMBOL(put_io_context);
void exit_io_context(struct task_struct *task)
{
struct io_context *ioc;
struct io_cq *icq;
struct hlist_node *n;
unsigned long flags;

task_lock(task);
ioc = task->io_context;
task->io_context = NULL;
task_unlock(task);

atomic_dec(&ioc->nr_tasks);
if (!atomic_dec_and_test(&ioc->nr_tasks)) {
put_io_context(ioc);
return;
}

/*
* Need ioc lock to walk icq_list and q lock to exit icq. Perform
* reverse double locking. Read comment in ioc_release_fn() for
* explanation on the nested locking annotation.
*/
retry:
spin_lock_irqsave_nested(&ioc->lock, flags, 1);
hlist_for_each_entry(icq, n, &ioc->icq_list, ioc_node) {
if (icq->flags & ICQ_EXITED)
continue;
if (spin_trylock(icq->q->queue_lock)) {
ioc_exit_icq(icq);
spin_unlock(icq->q->queue_lock);
} else {
spin_unlock_irqrestore(&ioc->lock, flags);
cpu_relax();
goto retry;
}
}
spin_unlock_irqrestore(&ioc->lock, flags);

put_io_context(ioc);
}

Expand All @@ -168,7 +207,7 @@ void ioc_clear_queue(struct request_queue *q)
struct io_context *ioc = icq->ioc;

spin_lock(&ioc->lock);
ioc_exit_icq(icq);
ioc_destroy_icq(icq);
spin_unlock(&ioc->lock);
}
}
Expand Down
1 change: 1 addition & 0 deletions include/linux/iocontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
enum {
ICQ_IOPRIO_CHANGED = 1 << 0,
ICQ_CGROUP_CHANGED = 1 << 1,
ICQ_EXITED = 1 << 2,

ICQ_CHANGED_MASK = ICQ_IOPRIO_CHANGED | ICQ_CGROUP_CHANGED,
};
Expand Down

0 comments on commit 621032a

Please sign in to comment.