On Wed, Jul 12, 2023 at 03:19:59PM +0000, Tage Johansson wrote:
> Hello,
> 
> While writing some tests for the Rust bindings, I discovered a
> memory leak with Valgrind due to a completion callback not being
> freed. More specificly, the completion callback of `aio_opt_info()`
> doesn't seem to be if `aio_opt_info()` failes. In this case,
> `aio_opt_info()` was called in the connected state, so it should
> indeed fail, but it should perhaps also call the free function
> associated with the callback.
> 
> According to libnbd(3):
> 
>     Callback lifetimes
>     You can associate an optional free function with callbacks. Libnbd will
>     call this function when the callback will not be called again by libnbd,
>     including in the case where the API fails.

I wanted to see / remember exactly how this was supposed to work, so I
picked nbd_aio_pread which has a callback.  Does this free the
callback in all basic error cases?  The answer is yes:

  int64_t
  nbd_aio_pread (
    struct nbd_handle *h, void *buf, size_t count, uint64_t offset,
    nbd_completion_callback completion_callback, uint32_t flags
  )
  {
  ...
    p = aio_pread_in_permitted_state (h);
    if (unlikely (!p)) {
      ret = -1;
      goto out;
    }
  ...
  out:
  ...
   FREE_CALLBACK (completion_callback);

> After studying the code in lib/opt.c, I think that the situation with
> synchronous callbacks like `nbd_opt_list()` is even worse. Here the list
> callback will not be freed if the internal call to 
> `nbd_unlocked_aio_opt_list()
> ` failes, but it will be freed if the completion callback got an error which 
> is
> returned by `nbd_opt_list()`.
> 
> 
>     /* Issue NBD_OPT_LIST and wait for the reply. */
>     int
>     nbd_unlocked_opt_list (struct nbd_handle *h, nbd_list_callback *list)
>     {
>       struct list_helper s = { .list = *list };
>       nbd_list_callback l = { .callback = list_visitor, .user_data = &s };
>       nbd_completion_callback c = { .callback = list_complete, .user_data = &s
>     };
> 
>       if (nbd_unlocked_aio_opt_list (h, &l, &c) == -1)
> 
> Here the list callback has not been freed.

Yes, I believe this is just a bug in the implementation of
nbd_unlocked_opt_list which should be fixed (to use FREE_CALLBACK
along this path).

>         return -1;
> 
>       SET_CALLBACK_TO_NULL (*list);
>       if (wait_fo_option (h) == -1)
>         return -1;
>       if (s.err) {
> 
> But here I think that the callback has been freed.

It took me a while to figure it out, but it seems so.  The callbacks
are copied to h->opt_cb.fn.list and opt_cb.fn.context and then freed
in the state machine.

>         set_error (s.err, "server replied with error to list request");
>         return -1;
>       }
>       return s.count;
>     }
>
> The problem is that if `nbd_opt_list()` returns an error, I as a
> user cannot know wether I should free the data associated with the
> list callback myself or if that has been done already.

It seems like it's just a missing call to FREE_CALLBACK.

> Maybe I am just misunderstanding the code or the API, but if not
> then I wonder how I should know when I need to free the user data
> associated with a callback myself and when that is done by libnbd?

No, it should work as in the documentation, else it's a bug.

Rich.

> 
> Best regards,
> 
> Tage
> 
> 

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-builder quickly builds VMs from scratch
http://libguestfs.org/virt-builder.1.html
_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs

Reply via email to