gstreamer/ext/wayland/wlbuffer.c
George Kiagiadakis 3058fe8d98 waylandsink: take into account the case where a pool may be destroyed together with GstWlDisplay
There are two cases covered here:
1) The GstWlDisplay forces the release of the last buffer and the pool
   gets destroyed in this context, which means it unregisters all the
   other buffers from the GstWlDisplay as well and the display->buffers
   hash table gets corrupted because it is iterating.
2) The pool and its buffers get destroyed concurrently from another
   thread while GstWlDisplay is finalizing and many things get corrupted.
2014-10-11 14:57:14 +02:00

233 lines
8.5 KiB
C

/* GStreamer Wayland video sink
*
* Copyright (C) 2014 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*/
/* GstWlBuffer wraps wl_buffer and provides a mechanism for preventing
* buffers from being re-used while the compositor is using them. This
* is achieved by adding a reference to the GstBuffer as soon as its
* associated wl_buffer is sent to the compositor and by removing this
* reference as soon as the compositor sends a wl_buffer::release message.
*
* This mechanism is a bit complicated, though, because it adds cyclic
* references that can be dangerous. The reference cycles looks like:
*
* ----------------
* | GstWlDisplay | ---------------------------->
* ---------------- |
* |
* V
* ----------------- ------------- ---------------
* | GstBufferPool | --> | GstBuffer | ==> | GstWlBuffer |
* | | <-- | | <-- | |
* ----------------- ------------- ---------------
*
* A GstBufferPool normally holds references to its GstBuffers and each buffer
* holds a reference to a GstWlBuffer (saved in the GstMiniObject qdata).
* When a GstBuffer is in use, it holds a reference back to the pool and the
* pool doesn't hold a reference to the GstBuffer. When the GstBuffer is unrefed
* externally, it returns back to the pool and the pool holds again a reference
* to the buffer.
*
* Now when the compositor is using a buffer, the GstWlBuffer also holds a ref
* to the GstBuffer, which prevents it from returning to the pool. When the
* last GstWlBuffer receives a release event and unrefs the last GstBuffer,
* the GstBufferPool will be able to stop and if no-one is holding a strong
* ref to it, it will be destroyed. This will destroy the pool's GstBuffers and
* also the GstWlBuffers. This will all happen in the same context of the last
* gst_buffer_unref, which will be called from the buffer_release() callback.
*
* The problem here lies in the fact that buffer_release() will be called
* from the event loop thread of GstWlDisplay, so it's as if the display
* holds a reference to the GstWlBuffer, but without having an actual reference.
* When we kill the display, there is no way for the GstWlBuffer, the associated
* GstBuffer and the GstBufferPool to get destroyed, so we are going to leak a
* fair ammount of memory.
*
* Normally, this rarely happens, because the compositor releases buffers
* almost immediately and when waylandsink stops, they are already released.
*
* However, we want to be absolutely certain, so a solution is introduced
* by registering all the GstWlBuffers with the display and explicitly
* releasing all the buffer references as soon as the display is destroyed.
*
* When the GstWlDisplay is finalized, it takes a reference to all the
* registered GstWlBuffers and then calls gst_wl_buffer_force_release_and_unref,
* which releases the potential reference to the GstBuffer, destroys the
* underlying wl_buffer and removes the reference that GstWlDisplay is holding.
* At that point, either the GstBuffer is alive somewhere and still holds a ref
* to the GstWlBuffer, which it will release when it gets destroyed, or the
* GstBuffer was destroyed in the meantime and the GstWlBuffer gets destroyed
* as soon as we remove the reference that GstWlDisplay holds.
*/
#include "wlbuffer.h"
GST_DEBUG_CATEGORY_EXTERN (gstwayland_debug);
#define GST_CAT_DEFAULT gstwayland_debug
G_DEFINE_TYPE (GstWlBuffer, gst_wl_buffer, G_TYPE_OBJECT);
static G_DEFINE_QUARK (GstWlBufferQDataQuark, gst_wl_buffer_qdata);
static void
gst_wl_buffer_dispose (GObject * gobject)
{
GstWlBuffer *self = GST_WL_BUFFER (gobject);
GST_TRACE_OBJECT (self, "dispose");
/* if the display is shutting down and we are trying to dipose
* the GstWlBuffer from another thread, unregister_buffer() will
* block and in the end the display will increase the refcount
* of this GstWlBuffer, so it will not be finalized */
if (self->display)
gst_wl_display_unregister_buffer (self->display, self);
G_OBJECT_CLASS (gst_wl_buffer_parent_class)->dispose (gobject);
}
static void
gst_wl_buffer_finalize (GObject * gobject)
{
GstWlBuffer *self = GST_WL_BUFFER (gobject);
GST_TRACE_OBJECT (self, "finalize");
if (self->wlbuffer)
wl_buffer_destroy (self->wlbuffer);
G_OBJECT_CLASS (gst_wl_buffer_parent_class)->finalize (gobject);
}
static void
gst_wl_buffer_class_init (GstWlBufferClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->dispose = gst_wl_buffer_dispose;
object_class->finalize = gst_wl_buffer_finalize;
}
static void
gst_wl_buffer_init (GstWlBuffer * self)
{
}
static void
buffer_release (void *data, struct wl_buffer *wl_buffer)
{
GstWlBuffer *self = data;
GST_LOG_OBJECT (self, "wl_buffer::release (GstBuffer: %p)", self->gstbuffer);
self->used_by_compositor = FALSE;
/* unref should be last, because it may end up destroying the GstWlBuffer */
gst_buffer_unref (self->gstbuffer);
}
static const struct wl_buffer_listener buffer_listener = {
buffer_release
};
static void
gstbuffer_disposed (GstWlBuffer * self)
{
g_assert (!self->used_by_compositor);
self->gstbuffer = NULL;
GST_TRACE_OBJECT (self, "owning GstBuffer was finalized");
/* this will normally destroy the GstWlBuffer, unless the display is
* finalizing and it has taken an additional reference to it */
g_object_unref (self);
}
GstWlBuffer *
gst_buffer_add_wl_buffer (GstBuffer * gstbuffer, struct wl_buffer *wlbuffer,
GstWlDisplay * display)
{
GstWlBuffer *self;
self = g_object_new (GST_TYPE_WL_BUFFER, NULL);
self->gstbuffer = gstbuffer;
self->wlbuffer = wlbuffer;
self->display = display;
gst_wl_display_register_buffer (self->display, self);
wl_buffer_add_listener (self->wlbuffer, &buffer_listener, self);
gst_mini_object_set_qdata ((GstMiniObject *) gstbuffer,
gst_wl_buffer_qdata_quark (), self, (GDestroyNotify) gstbuffer_disposed);
return self;
}
GstWlBuffer *
gst_buffer_get_wl_buffer (GstBuffer * gstbuffer)
{
return gst_mini_object_get_qdata ((GstMiniObject *) gstbuffer,
gst_wl_buffer_qdata_quark ());
}
void
gst_wl_buffer_force_release_and_unref (GstWlBuffer * self)
{
/* Force a buffer release.
* At this point, the GstWlDisplay has killed its event loop,
* so we don't need to worry about buffer_release() being called
* at the same time from the event loop thread */
if (self->used_by_compositor) {
GST_DEBUG_OBJECT (self, "forcing wl_buffer::release (GstBuffer: %p)",
self->gstbuffer);
self->used_by_compositor = FALSE;
gst_buffer_unref (self->gstbuffer);
}
/* Finalize this GstWlBuffer early.
* This method has been called as a result of the display shutting down,
* so we need to stop using any wayland resources and disconnect from
* the display. The GstWlBuffer stays alive, though, to avoid race
* conditions with the GstBuffer being destroyed from another thread.
* The last reference is either owned by the GstBuffer or by us and
* it will be released at the end of this function. */
GST_TRACE_OBJECT (self, "finalizing early");
wl_buffer_destroy (self->wlbuffer);
self->wlbuffer = NULL;
self->display = NULL;
/* remove the reference that the caller (GstWlDisplay) owns */
g_object_unref (self);
}
void
gst_wl_buffer_attach (GstWlBuffer * self, struct wl_surface *surface)
{
g_return_if_fail (self->used_by_compositor == FALSE);
wl_surface_attach (surface, self->wlbuffer, 0, 0);
/* Add a reference to the buffer. This represents the fact that
* the compositor is using the buffer and it should not return
* back to the pool and be re-used until the compositor releases it. */
gst_buffer_ref (self->gstbuffer);
self->used_by_compositor = TRUE;
}