[2/2] block: Handle multiwrite errors only when all requests have completed

Submitted by Kevin Wolf on July 1, 2010, 2:31 p.m.

Details

Message ID 1277994718-14443-3-git-send-email-kwolf@redhat.com
State New
Headers show

Commit Message

Kevin Wolf July 1, 2010, 2:31 p.m.
Don't try to be clever by freeing all temporary data and calling all callbacks
when the return value (an error) is certain. Doing so has at least two
important problems:

* The temporary data that is freed (qiov, possibly zero buffer) is still used
  by the requests that have not yet completed.
* Calling the callbacks for all requests in the multiwrite means for the caller
  that it may free buffers etc. which are still in use.

Just remember the error value and do the cleanup when all requests have
completed.

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 block.c |    5 +----
 1 files changed, 1 insertions(+), 4 deletions(-)

Comments

Stefan Hajnoczi July 2, 2010, 8:33 a.m.
On Thu, Jul 1, 2010 at 3:31 PM, Kevin Wolf <kwolf@redhat.com> wrote:
> Don't try to be clever by freeing all temporary data and calling all callbacks
> when the return value (an error) is certain. Doing so has at least two
> important problems:
>
> * The temporary data that is freed (qiov, possibly zero buffer) is still used
>  by the requests that have not yet completed.
> * Calling the callbacks for all requests in the multiwrite means for the caller
>  that it may free buffers etc. which are still in use.
>
> Just remember the error value and do the cleanup when all requests have
> completed.
>
> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
> ---
>  block.c |    5 +----
>  1 files changed, 1 insertions(+), 4 deletions(-)
>
> diff --git a/block.c b/block.c
> index 9719649..bff7d5a 100644
> --- a/block.c
> +++ b/block.c
> @@ -2056,14 +2056,11 @@ static void multiwrite_cb(void *opaque, int ret)
>
>     if (ret < 0 && !mcb->error) {
>         mcb->error = ret;
> -        multiwrite_user_cb(mcb);
>     }
>
>     mcb->num_requests--;
>     if (mcb->num_requests == 0) {
> -        if (mcb->error == 0) {
> -            multiwrite_user_cb(mcb);
> -        }
> +        multiwrite_user_cb(mcb);

I am a little confused at this point.  Here is the meat of
bdrv_aio_multiwrite() from the kevin/devel branch:

// Run the aio requests
mcb->num_requests = num_reqs;
for (i = 0; i < num_reqs; i++) {
    acb = bdrv_aio_writev(bs, reqs[i].sector, reqs[i].qiov,
        reqs[i].nb_sectors, multiwrite_cb, mcb);

    if (acb == NULL) {
        // We can only fail the whole thing if no request has been
        // submitted yet. Otherwise we'll wait for the submitted AIOs to
        // complete and report the error in the callback.
        if (i == 0) {
            reqs[i].error = -EIO;
            goto fail;
        } else {
            multiwrite_cb(mcb, -EIO);
            break;
        }
    }
}

If bdrv_aio_write() fails on request 2 out of 3, then
mcb->num_requests = 3 but only 2 requests were issued before breaking
the loop.

Now the 2 issued requests complete and call multiwrite_cb().  Since
was mcb->num_requests = 3, it never reaches zero and neither
multiwrite_user_cb(mcb) nor qemu_free(mcb) are ever called.

Is it safe to adjust mcb->num_requests = i before calling
multiwrite_cb() and breaking the loop in the failure case?  That way
the num->num_requests will reach zero.

I hesitate a little because previous requests could have completed,
multiwrite_cb() was called, and mcb->num_requests was decremented?
Then the value of i cannot be used for mcb->num_requests because
previous requests it counts have already completed.

Stefan
Christoph Hellwig July 2, 2010, 9:40 a.m.
On Thu, Jul 01, 2010 at 04:31:58PM +0200, Kevin Wolf wrote:
> Don't try to be clever by freeing all temporary data and calling all callbacks
> when the return value (an error) is certain. Doing so has at least two
> important problems:
> 
> * The temporary data that is freed (qiov, possibly zero buffer) is still used
>   by the requests that have not yet completed.
> * Calling the callbacks for all requests in the multiwrite means for the caller
>   that it may free buffers etc. which are still in use.
> 
> Just remember the error value and do the cleanup when all requests have
> completed.

Looks good.

Patch hide | download patch | download mbox

diff --git a/block.c b/block.c
index 9719649..bff7d5a 100644
--- a/block.c
+++ b/block.c
@@ -2056,14 +2056,11 @@  static void multiwrite_cb(void *opaque, int ret)
 
     if (ret < 0 && !mcb->error) {
         mcb->error = ret;
-        multiwrite_user_cb(mcb);
     }
 
     mcb->num_requests--;
     if (mcb->num_requests == 0) {
-        if (mcb->error == 0) {
-            multiwrite_user_cb(mcb);
-        }
+        multiwrite_user_cb(mcb);
         qemu_free(mcb);
     }
 }