mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 02:30:35 +00:00
491 lines
15 KiB
Text
491 lines
15 KiB
Text
Object relation types
|
|
---------------------
|
|
|
|
This document describes the relations between objects that exist in GStreamer.
|
|
It will also describe the way of handling the relation wrt locking and
|
|
refcounting.
|
|
|
|
parent-child relation
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
+---------+ +-------+
|
|
| parent | | child |
|
|
*--->| *----->| |
|
|
| F1|<-----* 1|
|
|
+---------+ +-------+
|
|
|
|
- properties
|
|
|
|
- parent has references to multiple children
|
|
- child has reference to parent
|
|
- reference fields protected with LOCK
|
|
- the reference held by each child to the parent is
|
|
NOT reflected in the refcount of the parent.
|
|
- the parent removes the floating flag of the child when taking
|
|
ownership.
|
|
- the application has valid reference to parent
|
|
- creation/destruction requires two unnested locks and 1 refcount.
|
|
|
|
- usage in GStreamer
|
|
|
|
GstBin -> GstElement
|
|
GstElement -> GstRealPad
|
|
|
|
- lifecycle
|
|
|
|
a) object creation
|
|
|
|
The application creates two object and holds a pointer
|
|
to them. The objects are initially FLOATING with a refcount
|
|
of 1.
|
|
|
|
+---------+ +-------+
|
|
*--->| parent | *--->| child |
|
|
| * | | |
|
|
| F1| | * F1|
|
|
+---------+ +-------+
|
|
|
|
b) establishing the parent-child relationship
|
|
|
|
The application then calls a method on the parent object to take
|
|
ownership of the child object. The parent performs the following
|
|
actions:
|
|
|
|
result = _set_parent (child, parent);
|
|
if (result) {
|
|
LOCK (parent);
|
|
ref_pointer = child;
|
|
|
|
.. update other data structures ..
|
|
UNLOCK (parent);
|
|
}
|
|
else {
|
|
.. child had parent ..
|
|
}
|
|
|
|
The _set_parent() method performs the following actions:
|
|
|
|
LOCK (child);
|
|
if (child->parent != NULL) {
|
|
UNLOCK (child);
|
|
return FALSE;
|
|
}
|
|
if (IS_FLOATING (child)) {
|
|
UNSET (child, FLOATING);
|
|
}
|
|
else {
|
|
_ref (child);
|
|
}
|
|
child->parent = parent;
|
|
UNLOCK (child);
|
|
_signal (PARENT_SET, child, parent);
|
|
return TRUE;
|
|
|
|
The function atomically checks if the child has no parent yet
|
|
and will set the parent if not. It will also sink the child, meaning
|
|
all floating references to the child are invalid now as it takes
|
|
over the refcount of the object.
|
|
|
|
Visually:
|
|
|
|
after _set_parent() returns TRUE:
|
|
|
|
+---------+ +-------+
|
|
*---->| parent | *-//->| child |
|
|
| * | | |
|
|
| F1|<-------------* 1|
|
|
+---------+ +-------+
|
|
|
|
after parent updates ref_pointer to child.
|
|
|
|
+---------+ +-------+
|
|
*---->| parent | *-//->| child |
|
|
| *--------->| |
|
|
| F1|<---------* 1|
|
|
+---------+ +-------+
|
|
|
|
- only one parent is able to _sink the same object because the
|
|
_set_parent() method is atomic.
|
|
- since only one parent is able to _set_parent() the object, only
|
|
one will add a reference to the object.
|
|
- since the parent can hold multiple references to children, we don't
|
|
need to lock the parent when locking the child. Many threads can
|
|
call _set_parent() on the children with the same parent, the parent
|
|
can then add all those to its lists.
|
|
|
|
Note: that the signal is emitted before the parent has added the
|
|
element to its internal data structures. This is not a problem
|
|
since the parent usually has his own signal to inform the app that
|
|
the child was reffed. One possible solution would be to update the
|
|
internal structure first and then perform a rollback if the _set_parent()
|
|
failed. This is not a good solution as iterators might grab the
|
|
'half-added' child too soon.
|
|
|
|
c) using the parent-child relationship
|
|
|
|
- since the initial floating reference to the child object became
|
|
invalid after giving it to the parent, any reference to a child
|
|
has at least a refcount > 1.
|
|
|
|
- this means that unreffing a child object cannot decrease the refcount
|
|
to 0. In fact, only the parent can destroy and dispose the child
|
|
object.
|
|
|
|
- given a reference to the child object, the parent pointer is only
|
|
valid when holding the child LOCK. Indeed, after unlocking the child
|
|
LOCK, the parent can unparent the child or the parent could even become
|
|
disposed. To avoid the parent dispose problem, when obtaining the
|
|
parent pointer, if should be reffed before releasing the child LOCK.
|
|
|
|
1) getting a reference to the parent.
|
|
|
|
- a referece is held to the child, so it cannot be disposed.
|
|
|
|
LOCK (child);
|
|
parent = _ref (child->parent);
|
|
UNLOCK (child);
|
|
|
|
.. use parent ..
|
|
|
|
_unref (parent);
|
|
|
|
2) getting a reference to a child
|
|
|
|
- a reference to a child can be obtained by reffing it before
|
|
adding it to the parent or by querying the parent.
|
|
|
|
- when requesting a child from the parent, a reference is held to
|
|
the parent so it cannot be disposed. The parent will use its
|
|
internal data structures to locate the child element and will
|
|
return a reference to it with an incremented refcount. The
|
|
requester should _unref() the child after usage.
|
|
|
|
|
|
d) destroying the parent-child relationship
|
|
|
|
- only the parent can actively destroy the parent-child relationship
|
|
this typically happens when a method is called on the parent to release
|
|
ownership of the child.
|
|
|
|
- a child shall never remove itself from the parent.
|
|
|
|
- since calling a method on the parent with the child as an argument
|
|
requires the caller to obtain a valid reference to the child, the child
|
|
refcount is at least > 1.
|
|
|
|
- the parent will perform the folowing actions:
|
|
|
|
LOCK (parent);
|
|
if (ref_pointer == child) {
|
|
ref_pointer = NULL;
|
|
|
|
.. update other data structures ..
|
|
UNLOCK (parent);
|
|
|
|
_unparent (child);
|
|
}
|
|
else {
|
|
UNLOCK (parent);
|
|
.. not our child ..
|
|
}
|
|
|
|
The _unparent() method performs the following actions:
|
|
|
|
LOCK (child);
|
|
if (child->parent != NULL) {
|
|
child->parent = NULL;
|
|
UNLOCK (child);
|
|
_signal (PARENT_UNSET, child, parent);
|
|
|
|
_unref (child);
|
|
}
|
|
else {
|
|
UNLOCK (child);
|
|
}
|
|
|
|
Since the _unparent() method unrefs the child object, it is possible that
|
|
the child pointer is invalid after this function. If the parent wants to
|
|
perform other actions on the child (such as signal emmision) it should
|
|
_ref() the child first.
|
|
|
|
|
|
single-reffed relation
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
+---------+ +---------+
|
|
*--->| object1 | *--->| object2 |
|
|
| *--------->| |
|
|
| 1| | 2|
|
|
+---------+ +---------+
|
|
|
|
- properties
|
|
|
|
- one object has a reference to another
|
|
- reference field protected with LOCK
|
|
- the reference held by the object is reflected in the
|
|
refcount of the other object.
|
|
- typically the other object can be shared among multiple
|
|
other objects where each ref is counted for in the
|
|
refcount.
|
|
- no object has ownership of the other.
|
|
- either shared state or copy-on-write.
|
|
- creation/destruction requires one lock and one refcount.
|
|
|
|
- usage
|
|
|
|
GstRealPad -> GstCaps
|
|
GstBuffer -> GstCaps
|
|
GstEvent -> GstCaps
|
|
GstEvent -> GstObject
|
|
GstMessage -> GstCaps
|
|
GstMessage -> GstObject
|
|
|
|
- lifecycle
|
|
|
|
a) Two objects exist unlinked.
|
|
|
|
+---------+ +---------+
|
|
*--->| object1 | *--->| object2 |
|
|
| * | | |
|
|
| 1| | 1|
|
|
+---------+ +---------+
|
|
|
|
b) establishing the single-reffed relationship
|
|
|
|
The second object is attached to the first one using a method
|
|
on the first object. The second object is reffed and a pointer
|
|
is updated in the first object using the following algorithm:
|
|
|
|
LOCK (object1);
|
|
if (object1->pointer)
|
|
_unref (object1->pointer);
|
|
object1->pointer = _ref (object2);
|
|
UNLOCK (object1);
|
|
|
|
After releasing the lock on the first object is is not sure that
|
|
object2 is still reffed from object1.
|
|
|
|
+---------+ +---------+
|
|
*--->| object1 | *--->| object2 |
|
|
| *--------->| |
|
|
| 1| | 2|
|
|
+---------+ +---------+
|
|
|
|
c) using the single-reffed relationship
|
|
|
|
The only way to access object2 is by holding a ref to it or by
|
|
getting the reference from object1.
|
|
Reading the object pointed to by object1 can be done like this:
|
|
|
|
LOCK (object1);
|
|
object2 = object1->pointer;
|
|
_ref (object2);
|
|
UNLOCK (object1);
|
|
|
|
.. use object2 ...
|
|
_unref (object2);
|
|
|
|
Depending on the type of the object, modifications can be done either
|
|
with copy-on-write or directly into the object.
|
|
|
|
Copy on write can practically only be done like this:
|
|
|
|
LOCK (object1);
|
|
object2 = object1->pointer;
|
|
object2 = _copy_on_write (object2);
|
|
... make modifications to object2 ...
|
|
UNLOCK (object1);
|
|
|
|
Releasing the lock has only a very small window where the copy_on_write
|
|
actually does not perform a copy:
|
|
|
|
LOCK (object1);
|
|
object2 = object1->pointer;
|
|
_ref (object2);
|
|
UNLOCK (object1);
|
|
|
|
.. object2 now has at least 2 refcounts making the next
|
|
copy-on-write make a real copy, unless some other thread
|
|
writes another object2 to object1 here ...
|
|
|
|
object2 = _copy_on_write (object2);
|
|
|
|
.. make modifications to object2 ...
|
|
|
|
LOCK (object1);
|
|
if (object1->pointer != object2) {
|
|
if (object1->pointer)
|
|
_unref (object1->pointer);
|
|
object1->pointer = gst_object_ref (object2);
|
|
}
|
|
UNLOCK (object1);
|
|
|
|
d) destroying the single-reffed relationship
|
|
|
|
The folowing algorithm removes the single-reffed link between
|
|
object1 and object2.
|
|
|
|
LOCK (object1);
|
|
_unref (object1->pointer);
|
|
object1->pointer = NULL;
|
|
UNLOCK (object1);
|
|
|
|
Which yields the following initial state again:
|
|
|
|
+---------+ +---------+
|
|
*--->| object1 | *--->| object2 |
|
|
| * | | |
|
|
| 1| | 1|
|
|
+---------+ +---------+
|
|
|
|
|
|
unreffed relation
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
+---------+ +---------+
|
|
*--->| object1 | *--->| object2 |
|
|
| *--------->| |
|
|
| 1|<---------* 1|
|
|
+---------+ +---------+
|
|
|
|
- properties
|
|
|
|
- two objects have references to each other
|
|
- both objects can only have 1 reference to another object.
|
|
- reference fields protected with LOCK
|
|
- the references held by each object are NOT reflected in the
|
|
refcount of the other object.
|
|
- no object has ownership of the other.
|
|
- typically each object is owned by a different parent.
|
|
- creation/destruction requires two nested locks and no refcounts.
|
|
|
|
- usage
|
|
|
|
- This type of link is used when the link is less important than
|
|
the existance of the objects, If one of the objects is disposed, so
|
|
is the link.
|
|
|
|
GstRealPad <-> GstRealPad (srcpad lock taken first)
|
|
|
|
- lifecycle
|
|
|
|
a) Two objects exist unlinked.
|
|
|
|
+---------+ +---------+
|
|
*--->| object1 | *--->| object2 |
|
|
| * | | |
|
|
| 1| | * 1|
|
|
+---------+ +---------+
|
|
|
|
b) establishing the unreffed relationship
|
|
|
|
Since we need to take two locks, the order in which these locks are
|
|
taken is very important or we might cause deadlocks. This lock order
|
|
must be defined for all unreffed relations. In these examples we always
|
|
lock object1 first and then object2.
|
|
|
|
LOCK (object1);
|
|
LOCK (object2);
|
|
object2->refpointer = object1;
|
|
object1->refpointer = object2;
|
|
UNLOCK (object2);
|
|
UNLOCK (object1);
|
|
|
|
c) using the unreffed relationship
|
|
|
|
Reading requires taking one of the locks and reading the corresponing
|
|
object. Again we need to ref the object before releasing the lock.
|
|
|
|
LOCK (object1);
|
|
object2 = _ref (object1->refpointer);
|
|
UNLOCK (object1);
|
|
|
|
.. use object2 ..
|
|
_unref (object2);
|
|
|
|
d) destroying the unreffed relationship
|
|
|
|
Because of the lock order we need to be careful when destroying this
|
|
Relation.
|
|
|
|
When only a reference to object1 is held:
|
|
|
|
LOCK (object1);
|
|
LOCK (object2);
|
|
object1->refpointer->refpointer = NULL;
|
|
object1->refpointer = NULL;
|
|
UNLOCK (object2);
|
|
UNLOCK (object1);
|
|
|
|
When only a reference to object2 is held we need to get a handle to the
|
|
other object fist so that we can lock it first. There is a window where
|
|
we need to release all locks and the relation could be invalid. To solve
|
|
this we check the relation after grabbing both locks and retry if the
|
|
relation changed.
|
|
|
|
retry:
|
|
LOCK (object2);
|
|
object1 = _ref (object2->refpointer);
|
|
UNLOCK (object2);
|
|
.. things can change here ..
|
|
LOCK (object1);
|
|
LOCK (object2);
|
|
if (object1 == object2->refpointer) {
|
|
/* relation unchanged */
|
|
object1->refpointer->refpointer = NULL;
|
|
object1->refpointer = NULL;
|
|
}
|
|
else {
|
|
/* relation changed.. retry */
|
|
UNLOCK (object2);
|
|
UNLOCK (object1);
|
|
_unref (object1);
|
|
goto retry;
|
|
}
|
|
UNLOCK (object2);
|
|
UNLOCK (object1);
|
|
_unref (object1);
|
|
|
|
When references are held to both objects. Note that it is not possible to
|
|
get references to both objects with the locks released since when the
|
|
references are taken and the locks are released, a concurrent update might
|
|
have changed the link, making the references not point to linked objects.
|
|
|
|
LOCK (object1);
|
|
LOCK (object2);
|
|
if (object1->refpointer == object2) {
|
|
object2->refpointer = NULL;
|
|
object1->refpointer = NULL;
|
|
}
|
|
else {
|
|
.. objects are not linked ..
|
|
}
|
|
UNLOCK (object2);
|
|
UNLOCK (object1);
|
|
|
|
|
|
double-reffed relation
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
+---------+ +---------+
|
|
*--->| object1 | *--->| object2 |
|
|
| *--------->| |
|
|
| 2|<---------* 2|
|
|
+---------+ +---------+
|
|
|
|
- properties
|
|
|
|
- two objects have references to each other
|
|
- reference fields protected with LOCK
|
|
- the references held by each object are reflected in the
|
|
refcount of the other object.
|
|
- no object has ownership of the other.
|
|
- typically each object is owned by a different parent.
|
|
- creation/destruction requires two locks and two refcounts.
|
|
|
|
- usage
|
|
|
|
Not used in GStreamer.
|
|
|
|
- lifecycle
|
|
|