Hi

On Wed, Oct 18, 2023 at 5:38 PM Edmund Raile <edmund.ra...@proton.me> wrote:
>
> Previous implementation of both functions was blocking and caused guest
> freezes / crashes on host clipboard owner change.
>  * use callbacks instead of waiting for GTK to deliver
>    clipboard content type evaluation and contents
>  * evaluate a serial in the info struct to discard old events
>
> Fixes: d11ebe2ca257 ("ui/gtk: add clipboard support")
> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1150
> Signed-off-by: Edmund Raile <edmund.ra...@proton.me>
> ---
>
> Gitlab user kolAflash is to credit for determining that the main issue
> of the QEMU-UI-GTK clipboard is the call to the blocking function
> gtk_clipboard_wait_is_text_available in gd_owner_change, causing guests
> to freeze / crash when GTK takes too long.
> Marc-André Lureau suggested:
>  * gd_clipboard_request might express the same issue due to using
>    gtk_clipboard_wait_for_text
>  * the callbacks could use the QemuClipboardInfo struct's serial field
>    to discard old events
>
> This patch implements asynchronous gd_clipboard_request and
> gd_owner_change with serial checking.
>
> What I haven't implemented is gd_clipboard_notify's
> QEMU_CLIPBOARD_RESET_SERIAL handling, I don't know how to.
>
> Please help me test this patch.
> The issue mentions the conditions, so far it has been stable.
> Note that you will need to build QEMU with `enable-gtk-clipboard`.
> command line options for qemu-vdagent:
> -device virtio-serial,packed=on,ioeventfd=on \
> -device virtserialport,name=com.redhat.spice.0,chardev=vdagent0 \
> -chardev qemu-vdagent,id=vdagent0,name=vdagent,clipboard=on,mouse=off \
> The guests spice-vdagent user service may have to be started manually.
>
> If testing is sufficient and shows no way to break this, we could undo
> or modify 29e0bfffab87d89c65c0890607e203b1579590a3
> to have the GTK UI's clipboard built-in by default again.
>
> Previous threads:
>  * https://lists.gnu.org/archive/html/qemu-devel/2023-09/msg06027.html
>  * https://lists.gnu.org/archive/html/qemu-devel/2023-10/msg04397.html
> I am not responding to either of the previous threads so as to not break
> anything in the mailing list by correcting my mistake in the subject.
>
>  ui/gtk-clipboard.c | 84 ++++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 70 insertions(+), 14 deletions(-)
>
> diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
> index 8d8a636fd1..07fe8b0ce1 100644
> --- a/ui/gtk-clipboard.c
> +++ b/ui/gtk-clipboard.c
> @@ -133,26 +133,85 @@ static void gd_clipboard_notify(Notifier *notifier, 
> void *data)
>      }
>  }
>
> +/*
> + * asynchronous clipboard text transfer callback
> + * called when host (gtk) is ready to deliver to guest
> + */
> +static void gd_clipboard_request_text_callback
> +    (GtkClipboard *clipboard, const gchar *text, gpointer data)
> +{
> +    QemuClipboardInfo *info = (QemuClipboardInfo *)data;
> +

No need for cast with a gpointer.

> +    if (!text || !qemu_clipboard_check_serial(info, true)) {
> +        return;
> +    }
> +
> +    qemu_clipboard_set_data(info->owner, info, QEMU_CLIPBOARD_TYPE_TEXT,
> +                            strlen(text), text, true);
> +    return;

drop that return; line

unref(info) (see below)

> +}
> +
> +/*
> + * asynchronous clipboard data transfer initiator
> + * guest requests, host delivers when ready
> + */
>  static void gd_clipboard_request(QemuClipboardInfo *info,
>                                   QemuClipboardType type)
>  {
>      GtkDisplayState *gd = container_of(info->owner, GtkDisplayState, cbpeer);
> -    char *text;
>
>      switch (type) {
>      case QEMU_CLIPBOARD_TYPE_TEXT:
> -        text = gtk_clipboard_wait_for_text(gd->gtkcb[info->selection]);
> -        if (text) {
> -            qemu_clipboard_set_data(&gd->cbpeer, info, type,
> -                                    strlen(text), text, true);
> -            g_free(text);
> -        }
> +        gtk_clipboard_request_text
> +            (gd->gtkcb[info->selection],
> +             gd_clipboard_request_text_callback, info);

You should ref() info here

>          break;
>      default:
>          break;
>      }
>  }
>
> +/*
> + * asynchronous clipboard text availability notification callback
> + * called when host (gtk) is ready to notify guest
> + */
> +static void gd_owner_change_text_callback
> +    (GtkClipboard *clipboard, const gchar *text, gpointer data)
> +{
> +    QemuClipboardInfo *info = (QemuClipboardInfo *)data;
> +
> +    static uint32_t notification_serial_last;
> +
> +    /*
> +     * performing the subtraction of uints as ints
> +     * is a neat trick to guard against rollover issues
> +     */
> +    if (!text ||
> +        (((int32_t)(info->serial - notification_serial_last)) <= 0))

You should only handle the last update, so a simple comparison with a
GtkDisplayState clipboard_request_serial field should do.

> +    {
> +        goto end;
> +    }
> +
> +    notification_serial_last = info->serial;
> +
> +    info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
> +    qemu_clipboard_update(info);
> +
> +    goto end;

drop that line

> +
> +end:
> +    /*
> +     * this notification info struct is temporary
> +     * and can safely be freed after use
> +     */
> +    qemu_clipboard_info_unref(info);
> +    return;

needless return;

> +}
> +
> +/*
> + * asynchronous clipboard data availability notification initiator
> + * host notifies guest when ready
> + */
>  static void gd_owner_change(GtkClipboard *clipboard,
>                              GdkEvent *event,
>                              gpointer data)
> @@ -160,22 +219,19 @@ static void gd_owner_change(GtkClipboard *clipboard,
>      GtkDisplayState *gd = data;
>      QemuClipboardSelection s = gd_find_selection(gd, clipboard);
>      QemuClipboardInfo *info;
> +    static uint32_t notification_serial;

You should use a GtkDisplayState field instead.
>
>      if (gd->cbowner[s]) {
>          /* ignore notifications about our own grabs */
>          return;
>      }
>
> -
>      switch (event->owner_change.reason) {
>      case GDK_OWNER_CHANGE_NEW_OWNER:
>          info = qemu_clipboard_info_new(&gd->cbpeer, s);
> -        if (gtk_clipboard_wait_is_text_available(clipboard)) {
> -            info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
> -        }
> -
> -        qemu_clipboard_update(info);
> -        qemu_clipboard_info_unref(info);
> +        info->serial = ++notification_serial;
> +        gtk_clipboard_request_text
> +            (clipboard, gd_owner_change_text_callback, info);
>          break;
>      default:
>          qemu_clipboard_peer_release(&gd->cbpeer, s);
> --
> 2.42.0
>
>
>


--
Marc-André Lureau

Reply via email to