[Spice-devel] [PATCH spice-gtk] webdav: don't buffer input from phodav

Frediano Ziglio fziglio at redhat.com
Wed Jul 3 08:44:14 UTC 2019


> 
> Hi again,
> 
> On Mon, Jul 1, 2019 at 3:16 PM Jakub Janku <jjanku at redhat.com> wrote:
> >
> > Hi,
> >
> > On Mon, Jul 1, 2019 at 1:02 PM Frediano Ziglio <fziglio at redhat.com> wrote:
> > >
> > > >
> > > > The current approach with OutputQueue in webdav has several problems:
> > > >
> > > > * if the connection is slow, webdav keeps reading from phodav
> > > > and pushing messages to the internal channel xmit_queue.
> > > > This way, the queue can grow very quickly and the whole file
> > > > that is being transferred using webdav essentially gets loaded into
> > > > memory.
> > > >
> > > > * spice channel first flushes all messages in the xmit_queue and
> > > > then proceeds to reading. If webdav floods the xmit_queue with
> > > > a ton of messages, spice channel does not leave iterate_write until
> > > > the queue gets empty. This way, reading from the channel is blocked
> > > > till the whole file is transferred.
> > > >
> > > > * OutputQueue uses g_output_stream_flush_async() on
> > > > SpiceVmcOutputStream
> > > > that does not implement flush
> > > >
> > > > To solve these issues, don't read from phodav until the last message
> > > > for a given client is written out to the socket.
> > > > (main channel currently uses the same approach when transferring files)
> > > >
> > > > OutputQueue used an idle function to schedule the write and then
> > > > called mux_pushed_cb which started reading from phodav with priority
> > > > G_PRIORITY_DEFAULT.
> > > > Since this new approach does not utilize the idle scheduling,
> > > > lower the priority in client_start_read() to G_PRIORITY_DEFAULT_IDLE
> > > > to make sure webdav does not block other channels.
> > > >
> > >
> > > This sounds a bit worrying. Why with all coroutine/async code we
> > > have we should have a blockage? This sounds wrong.
> >
> > To be honest, I did not spent much time looking at why this blocks.
> > Lowering the priority of webdav makes sense to me regardless of this issue.
> > Another thing is that with your spice-server series concerning
> > spicevmc and DoS, this isn't an issue.
> 
> I did some more testing and here's what I've found out:
> 
> Channel display uses g_coroutine_signal_emit() to emit the
> "display-invalidate" signal.
> This function uses an idle function (G_PRIORITY_DEFAULT_IDLE = 200) to
> emit the signal in the main context.
> 
> From Gtk docs (G_PRIORITY_HIGH_IDLE = 100; larger number means lower
> priority):
> "GTK+ uses G_PRIORITY_HIGH_IDLE + 10 for resizing operations, and
> G_PRIORITY_HIGH_IDLE + 20 for redrawing operations."
> 
> This means that if the client_start_read() in webdav uses
> G_PRIORITY_DEFAULT= 0, it can block these two operations rather easily
> causing the widget not to redraw. But as I said, this shouldn't be an
> issue if the channel was throttled down by the server.
> 
> So is it ok to lower the priority in webdav to G_PRIORITY_DEFAULT_IDLE?
> 

It sounds a good reason. Maybe copy this explanation too.
Saying just "block other channels" with all this code seems to suggest more
a synchonous code which will block than a priority issue. Also I think that
this patch should at least reduce the blocking in this case.

By the way, I found some time to test my patch and update your patch to
use asynchronous writing to the channel in order to remove the other
potential blockage.

> Cheers,
> Jakub
> > >
> > > > Also implement spice_webdav_channel_reset(). This is necessary because
> > > > spice_vmc_write_async() references the channel. If the channel is to be
> > > > disconnected, the write operations need to be cancelled so that
> > > > the references to the channel are released asap. Otherwise,
> > > > spice session would be stuck waiting for the channel to finalize.
> > > >
> > > > Signed-off-by: Jakub Janků <jjanku at redhat.com>
> > > > ---
> > > >
> > > > Note: I left the OutputQueue code still in place since I'm considering
> > > > using it for the drag&drop functionality.
> > > >
> > > >  src/channel-webdav.c | 49 ++++++++++++++++++++++++++++++++------------
> > > >  1 file changed, 36 insertions(+), 13 deletions(-)
> > > >
> > > > diff --git a/src/channel-webdav.c b/src/channel-webdav.c
> > > > index f5a38ad..66f8a5b 100644
> > > > --- a/src/channel-webdav.c
> > > > +++ b/src/channel-webdav.c
> > > > @@ -45,13 +45,12 @@
> > > >   * Since: 0.24
> > > >   */
> > > >
> > > > -typedef struct _OutputQueue OutputQueue;
> > > > +/* typedef struct _OutputQueue OutputQueue; */
> > > >
> > > >  struct _SpiceWebdavChannelPrivate {
> > > >      SpiceVmcStream *stream;
> > > >      GCancellable *cancellable;
> > > >      GHashTable *clients;
> > > > -    OutputQueue *queue;
> > > >
> > > >      gboolean demuxing;
> > > >      struct _demux {
> > > > @@ -65,6 +64,7 @@ G_DEFINE_TYPE_WITH_PRIVATE(SpiceWebdavChannel,
> > > > spice_webdav_channel, SPICE_TYPE_
> > > >
> > > >  static void spice_webdav_handle_msg(SpiceChannel *channel, SpiceMsgIn
> > > >  *msg);
> > > >
> > > > +#if 0
> > > >  struct _OutputQueue {
> > > >      GOutputStream *output;
> > > >      gboolean flushing;
> > > > @@ -179,6 +179,8 @@ static void output_queue_push(OutputQueue *q, const
> > > > guint8 *buf, gsize size,
> > > >          q->idle_id = g_idle_add(output_queue_idle, q);
> > > >  }
> > > >
> > > > +#endif
> > > > +
> > > >  typedef struct Client
> > > >  {
> > > >      guint refs;
> > > > @@ -227,10 +229,17 @@ static void remove_client(Client *client)
> > > >      g_hash_table_remove(client->self->priv->clients, &client->id);
> > > >  }
> > > >
> > > > -static void mux_pushed_cb(OutputQueue *q, gpointer user_data)
> > > > +#define MAX_MUX_SIZE G_MAXUINT16
> > > > +
> > > > +static void
> > > > +mux_msg_flushed_cb(GObject *source_object,
> > > > +                   GAsyncResult *result,
> > > > +                   gpointer user_data)
> > > >  {
> > > >      Client *client = user_data;
> > > >
> > > > +    g_output_stream_write_finish(G_OUTPUT_STREAM(source_object),
> > > > result,
> > > > NULL);
> > > > +
> > > >      if (client->mux.size == 0 ||
> > > >          !client_start_read(client)) {
> > > >          remove_client(client);
> > > > @@ -239,14 +248,13 @@ static void mux_pushed_cb(OutputQueue *q,
> > > > gpointer
> > > > user_data)
> > > >      client_unref(client);
> > > >  }
> > > >
> > > > -#define MAX_MUX_SIZE G_MAXUINT16
> > > > -
> > > >  static void server_reply_cb(GObject *source_object,
> > > >                              GAsyncResult *res,
> > > >                              gpointer user_data)
> > > >  {
> > > >      Client *client = user_data;
> > > >      SpiceWebdavChannelPrivate *c = client->self->priv;
> > > > +    GOutputStream *mux_out;
> > > >      GError *err = NULL;
> > > >      gssize size;
> > > >
> > > > @@ -258,10 +266,15 @@ static void server_reply_cb(GObject
> > > > *source_object,
> > > >      g_return_if_fail(size >= 0);
> > > >      client->mux.size = size;
> > > >
> > > > -    output_queue_push(c->queue, (guint8 *)&client->mux.id,
> > > > sizeof(gint64),
> > > > NULL, NULL);
> > > > +    mux_out = g_io_stream_get_output_stream(G_IO_STREAM(c->stream));
> > > > +
> > > > +    g_output_stream_write(mux_out, (guint8 *)&client->mux.id,
> > > > sizeof(gint64), NULL, NULL);
> > > >      client->mux.size = GUINT16_TO_LE(client->mux.size);
> > > > -    output_queue_push(c->queue, (guint8 *)&client->mux.size,
> > > > sizeof(guint16), NULL, NULL);
> > > > -    output_queue_push(c->queue, (guint8 *)client->mux.buf, size,
> > > > (GFunc)mux_pushed_cb, client);
> > > > +    g_output_stream_write(mux_out, (guint8 *)&client->mux.size,
> > > > sizeof(guint16), NULL, NULL);
> > > > +    /* this internally uses spice_vmc_write_async(), priority is
> > > > ignored;
> > > > +     * the callback is invoked once the msg is written out to the
> > > > socket */
> > > > +    g_output_stream_write_async(mux_out, (guint8 *)client->mux.buf,
> > > > size,
> > > > +        G_PRIORITY_DEFAULT, client->cancellable, mux_msg_flushed_cb,
> > > > client);
> > >
> > > Why don't you use g_output_stream_writev_all_async here writing all 3
> > > buffers
> > > together and avoid blocking calls?
> >
> > Actually, all of these 3 calls are in a way synchronous -- look at
> > SpiceVmcOutputStream implementation,
> > they all create a new out message and append it to the channel's
> > xmit_queue.
> > The only difference is that with the async call, we can provide a
> > function that is called back once the message is written out.
> >
> > I agree that the 3 buffers could be written together, but writing them
> > separately wasn't introduced by me.
> >
> > > Well, it's available starting from GLib 2.60 so possibly you'll have
> > > to provide a replacement for GLib 2.60 using
> > > g_output_stream_write_all_async.
> > >
> > > >
> > > >      return;
> > > >
> > > > @@ -284,8 +297,9 @@ static bool client_start_read(Client *client)
> > > >      if (g_input_stream_is_closed(input)) {
> > > >          return false;
> > > >      }
> > > > +    /* use low priority to leave enough time for other channels */
> > > >      g_input_stream_read_async(input, client->mux.buf, MAX_MUX_SIZE,
> > > > -                              G_PRIORITY_DEFAULT, client->cancellable,
> > > > server_reply_cb,
> > > > +                              G_PRIORITY_DEFAULT_IDLE,
> > > > client->cancellable,
> > > > server_reply_cb,
> > > >                                client_ref(client));
> > > >      return true;
> > > >  }
> > > > @@ -540,9 +554,6 @@ static void
> > > > spice_webdav_channel_init(SpiceWebdavChannel
> > > > *channel)
> > > >      c->clients = g_hash_table_new_full(g_int64_hash, g_int64_equal,
> > > >                                         NULL, client_remove_unref);
> > > >      c->demux.buf = g_malloc0(MAX_MUX_SIZE);
> > > > -
> > > > -    GOutputStream *ostream =
> > > > g_io_stream_get_output_stream(G_IO_STREAM(c->stream));
> > > > -    c->queue = output_queue_new(ostream);
> > > >  }
> > > >
> > > >  static void spice_webdav_channel_finalize(GObject *object)
> > > > @@ -560,7 +571,6 @@ static void spice_webdav_channel_dispose(GObject
> > > > *object)
> > > >
> > > >      g_cancellable_cancel(c->cancellable);
> > > >      g_clear_object(&c->cancellable);
> > > > -    g_clear_pointer(&c->queue, output_queue_free);
> > > >      g_clear_object(&c->stream);
> > > >      g_hash_table_unref(c->clients);
> > > >
> > > > @@ -572,6 +582,18 @@ static void spice_webdav_channel_up(SpiceChannel
> > > > *channel)
> > > >      CHANNEL_DEBUG(channel, "up");
> > > >  }
> > > >
> > > > +static void spice_webdav_channel_reset(SpiceChannel *channel, gboolean
> > > > migrating)
> > > > +{
> > > > +    SpiceWebdavChannelPrivate *c;
> > > > +    c =
> > > > spice_webdav_channel_get_instance_private(SPICE_WEBDAV_CHANNEL(channel));
> > > > +
> > > > +    g_cancellable_cancel(c->cancellable);
> > > > +    c->demuxing = FALSE;
> > > > +    g_hash_table_remove_all(c->clients);
> > > > +
> > > > +
> > > > SPICE_CHANNEL_CLASS(spice_webdav_channel_parent_class)->channel_reset(channel,
> > > > migrating);
> > > > +}
> > > > +
> > > >  static void spice_webdav_channel_class_init(SpiceWebdavChannelClass
> > > >  *klass)
> > > >  {
> > > >      GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
> > > > @@ -581,6 +603,7 @@ static void
> > > > spice_webdav_channel_class_init(SpiceWebdavChannelClass *klass)
> > > >      gobject_class->finalize     = spice_webdav_channel_finalize;
> > > >      channel_class->handle_msg   = spice_webdav_handle_msg;
> > > >      channel_class->channel_up   = spice_webdav_channel_up;
> > > > +    channel_class->channel_reset = spice_webdav_channel_reset;
> > > >
> > > >      g_signal_override_class_handler("port-event",
> > > >                                      SPICE_TYPE_WEBDAV_CHANNEL,
> > >
> > > Frediano
> >
> > Thanks,
> > Jakub
> 


More information about the Spice-devel mailing list