summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorKent Overstreet <koverstreet@google.com>2013-03-02 15:25:46 +1100
committerStephen Rothwell <sfr@canb.auug.org.au>2013-03-07 14:27:10 +1100
commitd8e64d4aee52a1f69530d2284dd6b14887a508f8 (patch)
tree36fb8ac3b4d9c0cac11284a1fb3dc82485648ffd /fs
parent53740cbb4c9f664ace657bdc7e6bb5b24d4a3a15 (diff)
aio-use-cancellation-list-lazily-fix
The cancellation changes were fubar - we can't cancel a kiocb if it doesn't actually have a cancellation callback. The use of xchg() in aio_complete() was right - there we're marking the kiocb as completed - but we need to use cmpxchg() in kiocb_cancel() - a lock isn't sufficient since we're synchronizing with aio_complete() which isn't taking any locks. Signed-off-by: Kent Overstreet <koverstreet@google.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/aio.c32
1 files changed, 22 insertions, 10 deletions
diff --git a/fs/aio.c b/fs/aio.c
index 53038913d35f..ed42871059e4 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -224,28 +224,40 @@ static int aio_setup_ring(struct kioctx *ctx)
void kiocb_set_cancel_fn(struct kiocb *req, kiocb_cancel_fn *cancel)
{
- if (!req->ki_list.next) {
- struct kioctx *ctx = req->ki_ctx;
- unsigned long flags;
+ struct kioctx *ctx = req->ki_ctx;
+ unsigned long flags;
- spin_lock_irqsave(&ctx->ctx_lock, flags);
+ spin_lock_irqsave(&ctx->ctx_lock, flags);
+
+ if (!req->ki_list.next)
list_add(&req->ki_list, &ctx->active_reqs);
- spin_unlock_irqrestore(&ctx->ctx_lock, flags);
- }
req->ki_cancel = cancel;
+
+ spin_unlock_irqrestore(&ctx->ctx_lock, flags);
}
EXPORT_SYMBOL(kiocb_set_cancel_fn);
static int kiocb_cancel(struct kioctx *ctx, struct kiocb *kiocb,
struct io_event *res)
{
- kiocb_cancel_fn *cancel;
+ kiocb_cancel_fn *old, *cancel;
int ret = -EINVAL;
- cancel = xchg(&kiocb->ki_cancel, KIOCB_CANCELLED);
- if (!cancel || cancel == KIOCB_CANCELLED)
- return ret;
+ /*
+ * Don't want to set kiocb->ki_cancel = KIOCB_CANCELLED unless it
+ * actually has a cancel function, hence the cmpxchg()
+ */
+
+ cancel = ACCESS_ONCE(kiocb->ki_cancel);
+ do {
+ if (!cancel || cancel == KIOCB_CANCELLED)
+ return ret;
+
+ BUG();
+ old = cancel;
+ cancel = cmpxchg(&kiocb->ki_cancel, old, KIOCB_CANCELLED);
+ } while (cancel != old);
atomic_inc(&kiocb->ki_users);
spin_unlock_irq(&ctx->ctx_lock);