Skip to content

Commit

Permalink
[PATCH] aio: remove aio_max_nr accounting race
Browse files Browse the repository at this point in the history
AIO was adding a new context's max requests to the global total before
testing if that resulting total was over the global limit.  This let
innocent tasks get their new limit tested along with a racing guilty task
that was crossing the limit.  This serializes the _nr accounting with a
spinlock It also switches to using unsigned long for the global totals.
Individual contexts are still limited to an unsigned int's worth of
requests by the syscall interface.

The problem and fix were verified with a simple program that spun creating
and destroying a context while holding on to another long lived context.
Before the patch a task creating a tiny context could get a spurious EAGAIN
if it raced with a task creating a very large context that overran the
limit.

Signed-off-by: Zach Brown <[email protected]>
Cc: Benjamin LaHaise <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
Zach Brown authored and Linus Torvalds committed Nov 7, 2005
1 parent 0f6ed7c commit d55b5fd
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 14 deletions.
31 changes: 21 additions & 10 deletions fs/aio.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
#endif

/*------ sysctl variables----*/
atomic_t aio_nr = ATOMIC_INIT(0); /* current system wide number of aio requests */
unsigned aio_max_nr = 0x10000; /* system wide maximum number of aio requests */
static DEFINE_SPINLOCK(aio_nr_lock);
unsigned long aio_nr; /* current system wide number of aio requests */
unsigned long aio_max_nr = 0x10000; /* system wide maximum number of aio requests */
/*----end sysctl variables---*/

static kmem_cache_t *kiocb_cachep;
Expand Down Expand Up @@ -208,7 +209,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
return ERR_PTR(-EINVAL);
}

if (nr_events > aio_max_nr)
if ((unsigned long)nr_events > aio_max_nr)
return ERR_PTR(-EAGAIN);

ctx = kmem_cache_alloc(kioctx_cachep, GFP_KERNEL);
Expand All @@ -233,8 +234,14 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
goto out_freectx;

/* limit the number of system wide aios */
atomic_add(ctx->max_reqs, &aio_nr); /* undone by __put_ioctx */
if (unlikely(atomic_read(&aio_nr) > aio_max_nr))
spin_lock(&aio_nr_lock);
if (aio_nr + ctx->max_reqs > aio_max_nr ||
aio_nr + ctx->max_reqs < aio_nr)
ctx->max_reqs = 0;
else
aio_nr += ctx->max_reqs;
spin_unlock(&aio_nr_lock);
if (ctx->max_reqs == 0)
goto out_cleanup;

/* now link into global list. kludge. FIXME */
Expand All @@ -248,8 +255,6 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
return ctx;

out_cleanup:
atomic_sub(ctx->max_reqs, &aio_nr);
ctx->max_reqs = 0; /* prevent __put_ioctx from sub'ing aio_nr */
__put_ioctx(ctx);
return ERR_PTR(-EAGAIN);

Expand Down Expand Up @@ -374,7 +379,12 @@ void fastcall __put_ioctx(struct kioctx *ctx)
pr_debug("__put_ioctx: freeing %p\n", ctx);
kmem_cache_free(kioctx_cachep, ctx);

atomic_sub(nr_events, &aio_nr);
if (nr_events) {
spin_lock(&aio_nr_lock);
BUG_ON(aio_nr - nr_events > aio_nr);
aio_nr -= nr_events;
spin_unlock(&aio_nr_lock);
}
}

/* aio_get_req
Expand Down Expand Up @@ -1258,8 +1268,9 @@ asmlinkage long sys_io_setup(unsigned nr_events, aio_context_t __user *ctxp)
goto out;

ret = -EINVAL;
if (unlikely(ctx || (int)nr_events <= 0)) {
pr_debug("EINVAL: io_setup: ctx or nr_events > max\n");
if (unlikely(ctx || nr_events == 0)) {
pr_debug("EINVAL: io_setup: ctx %lu nr_events %u\n",
ctx, nr_events);
goto out;
}

Expand Down
5 changes: 3 additions & 2 deletions include/linux/aio.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ struct kioctx {
struct list_head active_reqs; /* used for cancellation */
struct list_head run_list; /* used for kicked reqs */

/* sys_io_setup currently limits this to an unsigned int */
unsigned max_reqs;

struct aio_ring_info ring_info;
Expand Down Expand Up @@ -234,7 +235,7 @@ static inline struct kiocb *list_kiocb(struct list_head *h)
}

/* for sysctl: */
extern atomic_t aio_nr;
extern unsigned aio_max_nr;
extern unsigned long aio_nr;
extern unsigned long aio_max_nr;

#endif /* __LINUX__AIO_H */
4 changes: 2 additions & 2 deletions kernel/sysctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -952,15 +952,15 @@ static ctl_table fs_table[] = {
.data = &aio_nr,
.maxlen = sizeof(aio_nr),
.mode = 0444,
.proc_handler = &proc_dointvec,
.proc_handler = &proc_doulongvec_minmax,
},
{
.ctl_name = FS_AIO_MAX_NR,
.procname = "aio-max-nr",
.data = &aio_max_nr,
.maxlen = sizeof(aio_max_nr),
.mode = 0644,
.proc_handler = &proc_dointvec,
.proc_handler = &proc_doulongvec_minmax,
},
#ifdef CONFIG_INOTIFY
{
Expand Down

0 comments on commit d55b5fd

Please sign in to comment.